"Flatten" or "Merge" Quartz 2D drawn lines - objective-c

I'm trying to figure out how you can flatten or merge alot of dynamicly drawn lines in Quartz 2D,
I'm drawing random lines over time on my stage, I add new line coordinates each time to an array and draw the array (with my drawRect and I push a new line in my array with a timer and a setNeedDisplay to acctually redraw all the previous lines plus the new one)
now the PROBLEM: after a while it starts to get slower and slower because the arrays are getting very long, so I though I should merge the coordinates into a flat image or something and clear the arrays to keep it memory healthy, but how do I do this?
This is my current workflow:
Call timer
[NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(drawdrawdraw) userInfo:nil repeats:YES];
Refresh the drawRect in my "drawdrawdraw" function
-(void)drawdrawdraw{
[self setNeedsDisplay];
}
my drawRect
-(void)drawRect:(CGRect)rect{
viewContext = UIGraphicsGetCurrentContext();
int count = [mijnArray_r_x count];
float r_x = (float)(random() % 768);
[mijnArray_r_x insertObject:[NSNumber numberWithFloat:r_x] atIndex:count];
float r_y = (float)(random() % 1004);
[mijnArray_r_y insertObject:[NSNumber numberWithFloat:r_y] atIndex:count];
float r_w = (float)(random() % 100);
[mijnArray_r_w insertObject:[NSNumber numberWithFloat:r_w] atIndex:count];
float r_a = (float)(random() % 100);
[mijnArray_r_a insertObject:[NSNumber numberWithFloat:r_a] atIndex:count];
CGContextSetLineWidth(viewContext, 2.0);
CGContextSetStrokeColorWithColor(viewContext, [UIColor blackColor].CGColor);
for (int k = 0; k <= count; k++) {
float temp_x = [[mijnArray_r_x objectAtIndex: k] floatValue];
float temp_y = [[mijnArray_r_y objectAtIndex: k] floatValue];
float temp_w = [[mijnArray_r_w objectAtIndex: k] floatValue];
float temp_a = [[mijnArray_r_a objectAtIndex: k] floatValue];
CGPoint pointpointpoint = CGPointMake(temp_x, temp_y);
CGPoint pointpointpointpoint = CGPointMake(temp_w, temp_a);
CGContextMoveToPoint(viewContext, pointpointpoint.x, pointpointpoint.y);
CGContextAddLineToPoint(viewContext, pointpointpoint.x - pointpointpointpoint.x, pointpointpoint.y + pointpointpointpoint.y);
CGContextStrokePath(viewContext);
}
} }

You can draw your lines into a UIImage by using UIGraphicsBeginImageContext(). This way, the time needed to draw will be constant, because in each iteration you're only drawing the cached image as a bitmap and the new lines you add.
- (void)drawRect:(CGRect)rect {
[self.currentImage drawInRect:[self bounds]];
UIGraphicsBeginImageContext([self bounds].size);
CGContextRef cgContext = UIGraphicsGetCurrentContext();
//Draw your lines into the cgContext here...
self.currentImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
This assumes that you have the property currentImage of type UIImage declared.

Related

How to draw a NSString to UIView with every character is vertical

I want to draw a NSString to UIView with every character is vertical,like this:
normal:
and after rotate:
i already have a solution:
- (void)drawTextInRect:(CGRect)rect {
for (int i = 0; i < self.text.length; i++) {
NSString *subText = [self.text substringWithRange:NSMakeRange(i, 1)];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
// Create text
CGContextSetFillColorWithColor(context, self.backgroundColor.CGColor);
// Rotate the context 90 degrees (convert to radians)
CGFloat rotate = (self.textDirection == MMLabelTextDirectionLeft)?(-M_PI_2):0;
CGAffineTransform transform1 = CGAffineTransformMakeRotation(rotate);
CGContextConcatCTM(context, transform1);
// Move the context back into the view
CGSize characterSize = [subText sizeWithAttributes:#{NSFontAttributeName:self.font}];
CGContextTranslateCTM(context, -20, i*characterSize.height);
// Draw the string
[subText drawInRect:rect withAttributes:#{NSFontAttributeName:self.font}];
// [subText drawAtPoint:CGPointMake(0, i*MAX(characterSize.width, characterSize.height)) withAttributes:#{NSFontAttributeName:self.font}];
// Clean up
CGContextRestoreGState(context);
}
}
My solution is not perfectly draw characters like 'r' or 'l',it makes me crazy!

How to drag a CALayer that is rotated and scaled?

I have an image in a CALayer (using the CALayers contents property). A CALayer has a transform property that can be use to scale and rotate. I can use that transform without difficulty when the CALayer is still.
But when I try to drag the CALayer (using mouseDragged:(NSEvent *)theEvent), then it all fells apart (the image gets flattened out as I drag).
-(void)mouseDragged:(NSEvent *)theEvent {
CGPoint loc = [self convertPoint:theEvent.locationInWindow fromView:nil];
CGPoint deltaLoc = ccpSub(loc, downLoc); // subtract two points
CGPoint newPos = ccpAdd(startPos, deltaLoc); // adds two points
[layer changePosition:newPos];
...
}
In the layer
-(void)changePosition:(CGPoint)newPos {
//prevent unintended animation actions
self.actions = #{#"position": [NSNull null]};
CGSize size = self.frame.size;
CGRect newRect = CGRectMake(newPos.x, newPos.y, size.width, size.height);
self.frame = newRect;
}
The problem is that I use CALayer's frame to dynamically change the location of the CALayer as I drag, but when the rotation angle is not zero, then the frame is not longer parallel to x and y axis. What is the best way to fix this problem?
One way to do it is to regenerate transforms for each incremental movement.
-(void)mouseDragged:(CGPoint)deltaLoc start:(CGPoint)startPos {
CGPoint newPos = ccpAdd(startPos, deltaLoc);
[self changePosition:newPos];
}
-(void)changePosition:(CGPoint)newPos {
//prevent unintended animation actions
self.actions = #{#"position": [NSNull null]};
double angle = [[self valueForKeyPath: #"transform.rotation"] doubleValue];
double scaleX = [[self valueForKeyPath: #"transform.scale.x"] doubleValue];
double scaleY = [[self valueForKeyPath: #"transform.scale.y"] doubleValue];
self.transform = CATransform3DIdentity;
CGSize size = self.frame.size;
CGRect newRect = CGRectMake(newPos.x, newPos.y, size.width, size.height);
self.frame = newRect;
[self applyTransformRotation:angle scale:ccp(scaleX, scaleY)];
}
-(void)applyTransformRotation:(double)angle scale:(CGPoint)scale {
self.transform = CATransform3DMakeRotation(angle, 0, 0, 1);
self.transform = CATransform3DScale(self.transform, scale.x, scale.y, 1);
}
I do not know if this is the most efficient way to do it, but it definitely works.

Cutting off the lower end of a float

This is more of a math question but I'm just not having any luck. Basically Ive got the code below to change cell colors depending on their row equivalent in the colors array. It all works very well but I would like it so that I could somehow cut off the bottom end of the brightness spectrum so that I never end up with anything say, below 0.2. Any suggestions on how to solve this would be appreciated.
-(IBAction)reloadTable {
float arrayCount = [masterListArray count];
float increment = (1.0 / arrayCount);
NSMutableArray *tempColor = [[NSMutableArray alloc]init];
colors = [[NSMutableArray alloc]init];
for (float brightness = 0.0; brightness < 1.0; brightness += increment) {
UIColor *color = [UIColor colorWithHue:50.0f/255.0f
saturation:1.0
brightness:brightness
alpha:1.0];
[tempColor addObject:color];
NSLog(#"brightness: %f", brightness);
}
colors = [[tempColor reverseObjectEnumerator] allObjects];
[self.tableView reloadData];
}
You can use some extra variables to make sure you always end up between 1.0 and the bottom end. In the below code we use the number of cells to iterate through (better practice than what you had before) and start with brightness 1.0 and end at your bottom end. Also no need to use tempColors.
-(IBAction)reloadTable {
float arrayCount = [masterListArray count];
float bottomEnd = 0.2;
float brightnessRange = 1.0 - bottomEnd;
float increment = (brightnessRange / arrayCount);
colors = [NSMutableArray array];
for (int i = 0; i < arrayCount; i++) {
// We start with light and go darker per cell.
float brightness = bottomEnd + (brightnessRange - i * increment);
UIColor *color = [UIColor colorWithHue:50.0f/255.0f
saturation:1.0
brightness:brightness
alpha:1.0];
[colors addObject:color];
NSLog(#"brightness: %f", brightness);
}
[self.tableView reloadData];
}

How can I display an array of pixels on a NSWindow?

Very simple question... I have an array of pixels, how do I display them on the screen?
#define WIDTH 10
#define HEIGHT 10
#define SIZE WIDTH*HEIGHT
unsigned short pixels[SIZE];
for (int i = 0; i < WIDTH; i++) {
for (int j = 0; j < HEIGHT; j++) {
pixels[j*HEIGHT + i] = 0xFFFF;
}
}
That's it... now how can I show them on the screen?
Create a new "Cocoa Application" (if you don't know how to create a cocoa application go to Cocoa Dev Center)
Subclass NSView (if you don't know how to subclass a view read section "Create the NSView Subclass")
Set your NSWindow to size 400x400 on interface builder
Use this code in your NSView
#import "MyView.h"
#implementation MyView
#define WIDTH 400
#define HEIGHT 400
#define SIZE (WIDTH*HEIGHT)
#define BYTES_PER_PIXEL 2
#define BITS_PER_COMPONENT 5
#define BITS_PER_PIXEL 16
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
// Get current context
CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
// Colorspace RGB
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
// Pixel Matrix allocation
unsigned short *pixels = calloc(SIZE, sizeof(unsigned short));
// Random pixels will give you a non-organized RAINBOW
for (int i = 0; i < WIDTH; i++) {
for (int j = 0; j < HEIGHT; j++) {
pixels[i+ j*HEIGHT] = arc4random() % USHRT_MAX;
}
}
// Provider
CGDataProviderRef provider = CGDataProviderCreateWithData(nil, pixels, SIZE, nil);
// CGImage
CGImageRef image = CGImageCreate(WIDTH,
HEIGHT,
BITS_PER_COMPONENT,
BITS_PER_PIXEL,
BYTES_PER_PIXEL*WIDTH,
colorSpace,
kCGImageAlphaNoneSkipFirst,
// xRRRRRGGGGGBBBBB - 16-bits, first bit is ignored!
provider,
nil, //No decode
NO, //No interpolation
kCGRenderingIntentDefault); // Default rendering
// Draw
CGContextDrawImage(context, self.bounds, image);
// Once everything is written on screen we can release everything
CGImageRelease(image);
CGColorSpaceRelease(colorSpace);
CGDataProviderRelease(provider);
}
#end
There's a bunch of ways to do this. One of the more straightforward is to use CGContextDrawImage. In drawRect:
CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort];
CGDataProviderRef provider = CGDataProviderCreateWithData(nil, bitmap, bitmap_bytes, nil);
CGImageRef img = CGImageCreate(..., provider, ...);
CGDataProviderRelease(provider);
CGContextDrawImage(ctx, dstRect, img);
CGImageRelease(img);
CGImageCreate has a bunch of arguments which I've left out here, as the correct values will depend on what your bitmap format is. See the CGImage reference for details.
Note that, if your bitmap is static, it may make sense to hold on to the CGImageRef instead of disposing of it immediately. You know best how your application works, so you decide whether that makes sense.
I solved this problem by using an NSImageView with NSBitmapImageRep to create the image from the pixel values. There are lots of options how you create the pixel values. In my case, I used 32-bit pixels (RGBA). In this code, pixels is the giant array of pixel value. display is the outlet for the NSImageView.
NSBitmapImageRep *myBitmap;
NSImage *myImage;
unsigned char *buff[4];
unsigned char *pixels;
int width, height, rectSize;
NSRect myBounds;
myBounds = [display bounds];
width = myBounds.size.width;
height = myBounds.size.height;
rectSize = width * height;
memset(buff, 0, sizeof(buff));
pixels = malloc(rectSize * 4);
(fill in pixels array)
buff[0] = pixels;
myBitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:buff
pixelsWide:width
pixelsHigh:height
bitsPerSample:8
samplesPerPixel:4
hasAlpha:YES
isPlanar:NO
colorSpaceName:NSCalibratedRGBColorSpace
bitmapFormat:0
bytesPerRow:(4 * width)
bitsPerPixel:32];
myImage = [[NSImage alloc] init];
[myImage addRepresentation:myBitmap];
[display setImage: myImage];
[myImage release];
[myBitmap release];
free(pixels);

Having problems with CALayer

So I have a class that returns a CALayer that has an array of CALayers added as sublayers, but the problem is that it doesn't seem to be rendering any of them. I think I may just be missing something about how CALayers work as I'm not very familiar yet. Could someone please take a look at my code to see what I am doing wrong?
- (CALayer*)drawMap {
CALayer* theLayer = [CALayer layer];
for (int y = 0; y < [self.height intValue]; y++) {
for (int x = 0; x < [self.width intValue]; x++) {
CALayer* tileLayer = [CALayer layer];
tileLayer.bounds = CGRectMake((x * [self.tileWidth intValue]), (y * [self.tileHeight intValue]), [self.tileWidth intValue], [self.tileHeight intValue]);
[tileLayer setBackgroundColor:[UIColor colorWithHue:(CGFloat)x saturation:(CGFloat)y brightness:255 alpha:1.0].CGColor];
[theLayer addSublayer:tileLayer];
}
}
[theLayer setBounds:CGRectMake([self.width intValue] * [self.tileWidth intValue], [self.height intValue] * [self.tileHeight intValue], 10, 10)];
return theLayer;
}
This is the code that initializes and draws the map.
Map* myMap = [[Map alloc] initFromFile];
[myMap setWidth:[NSNumber numberWithInt:100]];
[myMap setHeight:[NSNumber numberWithInt:100]];
[self.view.layer insertSublayer:[myMap drawMap] above:self.view.layer];
I can provide the headers if needed, but I don't think the problem is there and I don't want a huge post.
I think the problem is the bounds you're setting to your parent layer:
[theLayer setBounds:
CGRectMake(
[self.width intValue] * [self.tileWidth intValue],
[self.height intValue] * [self.tileHeight intValue],
10, 10)];
That would appear to set the bounds so that it starts just exactly past all the sublayers you've just added and extends for 10 points in both directions. I think the following would be more appropriate:
[theLayer setBounds:
CGRectMake(
0,
0,
[self.width intValue] * [self.tileWidth intValue],
[self.height intValue] * [self.tileHeight intValue])];
The following also looks fishy:
[self.view.layer insertSublayer:[myMap drawMap] above:self.view.layer];
[myMap drawMap] will become a sublayer of self.view.layer. self.view.layer is not one of its own subviews so it won't know what to do with that above: instruction. You probably want just addSublayer:.
Finally, this:
tileLayer.bounds = CGRectMake((x * [self.tileWidth intValue]), (y * [self.tileHeight intValue]), [self.tileWidth intValue], [self.tileHeight intValue]);
gives every single one of your sublayers exactly the same frame. Much like UIViews, the bounds are the source area for information and the frame is where that information will be put within the super layer. I think probably you want to set frame.
Finally, UIColor +colorWithHue:... clamps hue and saturation to the range [0, 1] so you probably want (CGFloat)x / [self.width floatValue] and the equivalent for saturation to ensure all your tiles come out in different colours.
EDIT: so, tying all that together, I modified your code slightly so that I didn't have to write the full encompassing class and added it to the Xcode view-based project template as follows:
#define kTileWidth 3
#define kTileHeight 3
#define kWidth 100
#define kHeight 100
- (CALayer*)drawMap {
CALayer* theLayer = [CALayer layer];
for (int y = 0; y < kHeight; y++)
{
for (int x = 0; x < kWidth; x++)
{
CALayer* tileLayer = [CALayer layer];
tileLayer.frame = CGRectMake((x * kTileWidth), (y * kTileHeight), kTileWidth, kTileHeight);
[tileLayer setBackgroundColor:[UIColor colorWithHue:(CGFloat)x/kWidth saturation:(CGFloat)y/kHeight brightness:255 alpha:1.0].CGColor];
[theLayer addSublayer:tileLayer];
}
}
[theLayer setFrame:CGRectMake(0, 0, kWidth * kTileWidth, kHeight * kTileHeight)];
return theLayer;
}
// ...
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self.view.layer addSublayer:[self drawMap]];
}
That gives the hue/saturation plane familiar from many an art package.