CTFrameGetVisibleStringRange() returns 0 - objective-c

I have this code that generates an array of viewController while reading an NSAttributedString. After the first cycle, the function CTFrameGetVisibleStringRange() returns 0 even if there is more text to display.
- (void)buildFrames
{
/*here we do some setup - define the x & y offsets and create an empty frames array */
float frameXOffset = 20;
float frameYOffset = 20;
self.frames = [NSMutableArray array];
//buildFrames continues by creating a path and a frame for the view's bounds (offset slightly so we have a margin).
CGMutablePathRef path = CGPathCreateMutable();
// create an insect rect for drawing
CGRect textFrame = CGRectInset(self.bounds, frameXOffset, frameYOffset);
CGPathAddRect(path, NULL, textFrame );// add it to the path
// Create a frame setter with my attributed String
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributedString);
//This section declares textPos, which will hold the current position in the text.
//It also declares columnIndex, which will count how many columns are already created.
int textPos = 0;
int columnIndex = 0;
while (textPos < [attributedString length]) {
//The while loop here runs until we've reached the end of the text. Inside the loop we create a column bounds: colRect is a CGRect which depending on columnIndex holds the origin and size of the current column. Note that we are building columns continuously to the right (not across and then down).
CGPoint colOffset = CGPointMake(frameXOffset , frameYOffset);
CGRect columnRect = CGRectMake(0, 0 , textFrame.size.width, textFrame.size.height);
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, colRect);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(textPos, 0), path, NULL);
CFRange frameRange = CTFrameGetVisibleStringRange(frame);
// MY CUSTOM UIVIEW
LSCTView* content = [[[LSCTView alloc] initWithFrame: CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height)] autorelease];
content.backgroundColor = [UIColor clearColor];
content.frame = CGRectMake(colOffset.x, colOffset.y, columnRect.size.width, columnRect.size.height) ;
/************* CREATE A NEW VIEW CONTROLLER WITH view=content *********************/
textPos += frameRange.length;
CFRelease(path);
columnIndex++;
}
}

Did you change alignment for attributedString? I had this samme issue and found that it occurs in some cases when text alignment is set to kCTJustifiedTextAlignment, it should works fine with rest types.

Related

CGContextFillRects: No matching function for call

I'm trying to optimize the performance in one of my components. The component needs to draw some (10 to 200) rectangles in it's drawRect method, which is triggered about 20 times per second.
Everything works when I use the CGContextFillRect method on each CGRect separately. I want to test if grouping the drawing into one single call with CGContextFillRects on an array of CGRects would increase performance.
The method CGContextFillRects gives me a compiler error No matching function for call to 'CGContextFillRects'.
This code is inside a .mm file. Should I import something before the CGContextFillRects method can be used?
This is what i'm trying to do:
- (void) drawRect:(CGRect)rect{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextSetFillColorWithColor(context, self.fillColor.CGColor);
//check if some objects are present
if (self.leftDrawBuffer && self.rightDrawBuffer){
UInt32 xPosForRect = self.leftPadding;
NSMutableArray *rectsToFill = [[NSMutableArray alloc] init];
for (int drawBufferLRIndex = 0; drawBufferLRIndex < 2; drawBufferLRIndex++){
Float32 *drawBuffer_ptr = self.leftDrawBuffer;
if (drawBufferLRIndex > 0){
drawBuffer_ptr = self.rightDrawBuffer;
}
for (int i=0; i< kAmountOfBarsPerChannel; i=i+1){
Float32 amp = drawBuffer_ptr[i];
Float32 blockNumber = 1.0f;
UInt32 yPosForRect = self.bounds.size.height - self.heightPerBlock;
while (blockNumber <= self.blocksPerLine && blockNumber / self.blocksPerLine < amp){
CGRect rect= CGRectMake(xPosForRect, yPosForRect, self.widthPerBlock, self.heightPerBlock);
[rectsToFill addObject:[NSValue valueWithCGRect:rect]];
//Using the method below works and gives me the expected result
//CGContextFillRect(context, rect);
blockNumber++;
yPosForRect -= self.heightPerBlock + self.vPaddingPerBlock;
}
xPosForRect += self.widthPerBlock + self.hPaddingPerBlock;
}
}
//This is the added code where i try to use CGContextFillRects
//1 -> transform to a c array of CGRects
const CGRect *cRects[rectsToFill.count];
for (int i = 0; i < rectsToFill.count; ++i) {
CGRect rect = [[rectsToFill objectAtIndex:i] CGRectValue];
cRects[i] = &rect;
}
size_t size = rectsToFill.count;
//2 -> trigger the method to fill all rects at once
//this method gives me the compiler error 'No matching function for call to 'CGContextFillRects''
CGContextFillRects(context, cRects, size);
}
CGContextRestoreGState(context);
}
The problem is how you convert the rects to a C array. You make pointers to the rects that are temporarily stored on the stack. There are two problems with this. First, the rects are gone with each loop iteration, so you can't do that. Second, You should pass a pointer to an array of CGRects, not an array of pointers to CGRect.
This will likely solve it:
CGRect cRects[rectsToFill.count]; // Replace your lines from this
for (int i = 0; i < rectsToFill.count; ++i) {
CGRect rect = [[rectsToFill objectAtIndex:i] CGRectValue];
cRects[i] = rect;
}
size_t size = rectsToFill.count;
CGContextFillRects(context, cRects, size); // To this
Please note the re-declaration of the cRects array and the change in the assignment.

Memory leak with core graphics

I've been tasked to solve a memory leak with a custom objective class for a legacy app that uses garbage collection.
The class takes in NSData from a jpeg file and can thumb the image. There is a method to return a new NSData object with the newly resized image.
ImageThumber * imgt = [ImageThumber withNSData:dataObjectFromJpeg];
[imgt thumbImage:1024];
NSdata * smallImage = [imgt imageData];
[imgt thumbImage:256];
NSdata * extraSmallImage = [imgt imageData];
It does what it's supposed to do but it's been discovered that for every ImageThumber that's created it allocates a ImageIO_jpeg_Data object that's never deallocated. This was found in Instruments.
When created using ImageThumber withNSData:(NSData) it creates a CGImage and stores it in a private CGImageRef variable
I found that if thumbImage:(int) isn't called the ImageIO_jpeg_Data will deallocate when ImageThumber is deallocated which leads me to believe the problem lies somewhere within the thumbImage method. If thumbImage method is called multiple times it doesn't create extra ImageIO_jpeg_Data.
I have little experience with core graphics and garbage collection.
+(id)SAMImageDataWithNSData:(NSData *)data
{
SAMImageData * new = [[[self alloc] init] autorelease];
new.imageData = [NSMutableData dataWithData:data];
CFDataRef imgData = (CFDataRef)data;
CGDataProviderRef imgDataProvider;
imgDataProvider = CGDataProviderCreateWithCFData(imgData);
new->_cgImageRef = CGImageCreateWithJPEGDataProvider(imgDataProvider, NULL, true, kCGRenderingIntentDefault);
CGDataProviderRelease(imgDataProvider);
int width = (int)CGImageGetWidth(new->_cgImageRef);
int height = (int)CGImageGetHeight(new->_cgImageRef);
new.originalSize = NSMakeSize(width, height);
return new;
}
-(void)thumbImage:(int)length
{
/* simple logic to calculate new width and height */
//If the next line is commented out the problem doesn't exist.
//You just don't get the image resize.
[self resizeCGImageToWidth:newSize.width andHeight:newSize.height];
CFMutableDataRef workingData = (CFMutableDataRef)[[NSMutableData alloc] initWithCapacity:0];
CGImageDestinationRef dest;
dest = CGImageDestinationCreateWithData(workingData,kUTTypeJPEG,1,NULL);
CGImageDestinationAddImage(dest,_cgImageRef,NULL);
CGImageDestinationFinalize(dest);
CFRelease(dest);
self.imageData = (NSMutableData *)workingData;
}
This where I believe the problem exists:
- (void)resizeCGImageToWidth:(int)width andHeight:(int)height {
CGColorSpaceRef colorspace = CGImageGetColorSpace(_cgImageRef);
CGContextRef context = CGBitmapContextCreate(NULL, width, height,
CGImageGetBitsPerComponent(_cgImageRef),
CGImageGetBytesPerRow(_cgImageRef),
colorspace,
kCGImageAlphaPremultipliedFirst);
CGColorSpaceRelease(colorspace);
if(context == NULL)
return;
CGContextDrawImage(context, CGRectMake(0, 0, width, height), _cgImageRef);
_cgImageRef = CGBitmapContextCreateImage(context);
CGContextRelease(context);
}

Create monochrome CGImageRef (1-bit per pixel bitmap)

I'm trying to show a preview of an image in 1-bit monochrome, as in, not grayscale, but bitonal black and white. It's supposed to be an indication of how the image will look if it were faxed. Formats as low as 1-bit per pixel aren't available on OS X, only 8-bit grayscale. Is there any way to achieve this effect using Core Graphics or another framework (ideally with dithering)?
I know there's a filter called CIColorMonochrome but this only converts the image to grayscale.
The creation of a 1 bit deep NSImageRep (and also in the CG-world) is AFAIK not supported, So we have to do it manually. It might be useful to use CIImage for this task. Here I go the classical (you may call it old-fashioned) way. Here is a code that shows how we can do it. First a gray image is created from an NSImageRep so we have a well defined and simple format whatever the source image will be formatted (could also be a PDF file). The resulting gray image is the source for the bitonal image. Here is the code for creating a gray image: (without respecting the size / resolution of the source image, only the pixels count!):
- (NSBitmapImageRep *) grayRepresentationOf:(NSImageRep *)aRep
{
NSBitmapImageRep *newRep =
[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
pixelsWide:[aRep pixelsWide]
pixelsHigh:[aRep pixelsHigh]
bitsPerSample:8
samplesPerPixel:1
hasAlpha:NO //must be NO !
isPlanar:NO
colorSpaceName:NSCalibratedWhiteColorSpace
bytesPerRow:0
bitsPerPixel:0 ];
// this new imagerep has (as default) a resolution of 72 dpi
[NSGraphicsContext saveGraphicsState];
NSGraphicsContext *context = [NSGraphicsContext graphicsContextWithBitmapImageRep:newRep];
if( context==nil ){
NSLog( #"*** %s context is nil", __FUNCTION__ );
return nil;
}
[NSGraphicsContext setCurrentContext:context];
[aRep drawInRect:NSMakeRect( 0, 0, [newRep pixelsWide], [newRep pixelsHigh] )];
[NSGraphicsContext restoreGraphicsState];
return [newRep autorelease];
}
In the next method we create an NXBitmapImageRep (bits per pixel=1, samples per pixel=1) from a given NSImageRep (one of it's subclasses) and will use the method just given:
- (NSBitmapImageRep *) binaryRepresentationOf:(NSImageRep *)aRep
{
NSBitmapImageRep *grayRep = [aRep grayRepresentation];
if( grayRep==nil ) return nil;
NSInteger numberOfRows = [grayRep pixelsHigh];
NSInteger numberOfCols = [grayRep pixelsWide];
NSBitmapImageRep *newRep =
[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
pixelsWide:numberOfCols
pixelsHigh:numberOfRows
bitsPerSample:1
samplesPerPixel:1
hasAlpha:NO
isPlanar:NO
colorSpaceName:NSCalibratedWhiteColorSpace
bitmapFormat:0
bytesPerRow:0
bitsPerPixel:0 ];
unsigned char *bitmapDataSource = [grayRep bitmapData];
unsigned char *bitmapDataDest = [newRep bitmapData];
// here is the place to use dithering or error diffusion (code below)
// iterate over all pixels
NSInteger grayBPR = [grayRep bytesPerRow];
NSInteger binBPR = [newRep bytesPerRow];
NSInteger pWide = [newRep pixelsWide];
for( NSInteger row=0; row<numberOfRows; row++ ){
unsigned char *rowDataSource = bitmapDataSource + row*grayBPR;
unsigned char *rowDataDest = bitmapDataDest + row*binBPR;
NSInteger destCol = 0;
unsigned char bw = 0;
for( NSInteger col = 0; col<pWide; ){
unsigned char gray = rowDataSource[col];
if( gray>127 ) {bw |= (1<<(7-col%8)); };
col++;
if( (col%8 == 0) || (col==pWide) ){
rowDataDest[destCol] = bw;
bw = 0;
destCol++;
}
}
}
// save as PNG for testing and return
[[newRep representationUsingType:NSPNGFileType properties:nil] writeToFile:#"/tmp/bin_1.png" atomically:YES];
return [newRep autorelease];
}
For error diffusion I used the following code which changes directly the bitmap of the gray image. This is allowed because the gray image itself is no longer used.
// change bitmapDataSource : use Error-Diffusion
for( NSInteger row=0; row<numberOfRows-1; row++ ){
unsigned char *currentRowData = bitmapDataSource + row*grayBPR;
unsigned char *nextRowData = currentRowData + grayBPR;
for( NSInteger col = 1; col<numberOfCols; col++ ){
NSInteger origValue = currentRowData[col];
NSInteger newValue = (origValue>127) ? 255 : 0;
NSInteger error = -(newValue - origValue);
currentRowData[col] = newValue;
currentRowData[col+1] = clamp(currentRowData[col+1] + (7*error/16));
nextRowData[col-1] = clamp( nextRowData[col-1] + (3*error/16) );
nextRowData[col] = clamp( nextRowData[col] + (5*error/16) );
nextRowData[col+1] = clamp( nextRowData[col+1] + (error/16) );
}
}
clamp is a macro defined before the method
#define clamp(z) ( (z>255)?255 : ((z<0)?0:z) )
This makes the unsigned char bytes to have valid values (0<=z<=255)

How to create a PDF in iOS programmatically?

I want to create a PDF file in iOS ,there should be one table in the PDF, which is filled from one array. I already searched on Google, but didn't succeed. Any help is appreciated
Something like this routine to render the text:
- (CFRange)renderTextRange:(CFRange)currentRange andFrameSetter:(CTFramesetterRef)frameSetter intoRect:(CGRect)frameRect {
CGMutablePathRef framePath = CGPathCreateMutable();
CGPathAddRect(framePath, NULL, frameRect);
CTFrameRef frameRef = CTFramesetterCreateFrame(frameSetter, currentRange, framePath, NULL);
CGContextRef currentContext = UIGraphicsGetCurrentContext();
CGContextSaveGState(currentContext);
CGContextSetTextMatrix(currentContext, CGAffineTransformIdentity);
CGContextTranslateCTM(currentContext, 0, 792);
CGContextScaleCTM(currentContext, 1.0, -1.0);
CTFrameDraw(frameRef, currentContext);
CGContextRestoreGState(currentContext);
CGPathRelease(framePath);
currentRange = CTFrameGetVisibleStringRange(frameRef);
currentRange.location += currentRange.length;
currentRange.length = 0;
CFRelease(frameRef);
return currentRange;
}
And the following code snippet which calls it, assuming you have the context and any fonts etc created and in the appropriate variables. The following loop simply builds up the text line by line into an NSMutableAttributedString which you can then render:
CTFontRef splainFont = CTFontCreateWithName(CFSTR("Helvetica"), 10.0f, NULL);
CGFloat margin = 32.0f;
CGFloat sink = 8.0f;
NSMutableAttributedString *mainAttributedString = [[NSMutableAttributedString alloc] init];
NSMutableString *mainString = [[NSMutableString alloc] init];
// Ingredients is an NSArray of NSDictionaries
// But yours could be anything, or just an array of text.
for (Ingredient *ingredient in ingredients) {
NSString *ingredientText = [NSString stringWithFormat:#"%#\t%#
\n",ingredient.amount,ingredient.name];
[mainString appendString:ingredientText];
NSMutableAttributedString *ingredientAttributedText =
[[NSMutableAttributedString alloc] initWithString:ingredientText];
[ingredientAttributedText addAttribute:(NSString *)(kCTFontAttributeName)
value:(id)splainFont
range:NSMakeRange(0, [ingredientText length])];
[mainAttributedString appendAttributedString:ingredientAttributedText];
[ingredientAttributedText release];
}
Now you have your array written out with newlines to one NSMutableAttributedString you can render it, depending on your text you might want to render it out in a loop until the rendered location matches your text length. Something like:
// Render Main text.
CTFramesetterRef mainSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)mainAttributedString);
currentRange = [KookaDIS renderTextRange:currentRange
andFrameSetter:mainSetter
intoRect:pageRect];
// If not finished create new page and loop until we are.
while (!done) {
UIGraphicsBeginPDFPageWithInfo(pageRect, nil);
currentRange = [self renderTextRange:currentRange
andFrameSetter:mainSetter
intoRect:pageRect];
if (currentRange.location >= [mainString length]) {
done = TRUE;
}
}
The above code will need quite a bit of adapting I'm sure as it's hacked out of a project of my own so some variables (like the frame setter) won't exist and you need to close off the PDF context and release variables etc. Note how mainString is used to determine when the text has been rendered out.
It should give a clear enough indication of how to loop around an array or any other group to render an arbitrary length of text into a document.
Slight modifications to the while loop and the one render before entering it will let you render text out in multiple columns too.

Capture mouse cursor in screenshot

I am developing Mac desktop application, where I am capturing the screen using
CGImageRef screenShot = CGWindowListCreateImage(CGRectInfinite, kCGWindowListOptionAll, kCGNullWindowID, kCGWindowImageDefault);
The problem is, I am expecting it should show the mouse cursor too, but it's not showing.
Do I need to enable any settings for that?
I tried the following before calling this function:
CGDisplayShowCursor(kCGDirectMainDisplay);
CGAssociateMouseAndMouseCursorPosition(true);
, but it didn't work.
When I checked using following
bool bCursor = CGCursorIsDrawnInFramebuffer(); /* This returns false */
bCursor = CGCursorIsVisible(); /* This returns true */
, te value says the cursor was not drawn in the frame buffer, however the cursor is visible.
I suppose I only need to do is to draw the cursor in the frame buffer, how do I do this?.
it seems, framebuffer doesn't give me the mouse cursor, so i am drawing my own, this is the code snippet , might be help full to you guys,
-(CGImageRef)appendMouseCursor:(CGImageRef)pSourceImage{
// get the cursor image
NSPoint mouseLoc;
mouseLoc = [NSEvent mouseLocation]; //get cur
NSLog(#"Mouse location is x=%d,y=%d",(int)mouseLoc.x,(int)mouseLoc.y);
// get the mouse image
NSImage *overlay = [[[NSCursor arrowCursor] image] copy];
NSLog(#"Mouse location is x=%d,y=%d cursor width = %d, cursor height = %d",(int)mouseLoc.x,(int)mouseLoc.y,(int)[overlay size].width,(int)[overlay size].height);
int x = (int)mouseLoc.x;
int y = (int)mouseLoc.y;
int w = (int)[overlay size].width;
int h = (int)[overlay size].height;
int org_x = x;
int org_y = y;
size_t height = CGImageGetHeight(pSourceImage);
size_t width = CGImageGetWidth(pSourceImage);
int bytesPerRow = CGImageGetBytesPerRow(pSourceImage);
unsigned int * imgData = (unsigned int*)malloc(height*bytesPerRow);
// have the graphics context now,
CGRect bgBoundingBox = CGRectMake (0, 0, width,height);
CGContextRef context = CGBitmapContextCreate(imgData, width,
height,
8, // 8 bits per component
bytesPerRow,
CGImageGetColorSpace(pSourceImage),
CGImageGetBitmapInfo(pSourceImage));
// first draw the image
CGContextDrawImage(context,bgBoundingBox,pSourceImage);
// then mouse cursor
CGContextDrawImage(context,CGRectMake(0, 0, width,height),pSourceImage);
// then mouse cursor
CGContextDrawImage(context,CGRectMake(org_x, org_y, w,h),[overlay CGImageForProposedRect: NULL context: NULL hints: NULL] );
// assuming both the image has been drawn then create an Image Ref for that
CGImageRef pFinalImage = CGBitmapContextCreateImage(context);
CGContextRelease(context);
return pFinalImage; /* to be released by the caller */
}