1
votes

In my application i have one main view (with its UIGraphicsGetCurrentContext()) and several other CGContextRef canvases. All of them are the same size, the size of the screen - without scale factor.

I draw on the secondary canvases and at the end use CGBitmapContextCreateImage on the secondary canvases and CGContextDrawImage to draw on the main canvas.

On retina displays the result is poor and all lines look pixelated. How should i handle painting on retina displays?

The following is the code i use to draw a context to the main context:

void CocoaGraphicsContext::drawContext(GraphicsContext* context, double x, double y, Box* cropBox, Matrix3D* matrix)
{
    CGAffineTransform matInverted;

    if (matrix != NULL)
    {
        CGAffineTransform transMat = CGAffineTransformMake(matrix->vx1, matrix->vy1, matrix->vx2, matrix->vy2, matrix->tx, matrix->ty);
        matInverted = CGAffineTransformInvert(transMat);

        CGContextConcatCTM(_cgContext, transMat);
    }

    CGImageRef cgImage = CGBitmapContextCreateImage(((CocoaGraphicsContext*)context)->cgContext());

    CGContextSaveGState(_cgContext);
    CGContextSetInterpolationQuality(_cgContext, kCGInterpolationNone);

    bool shouldCrop = ((cropBox != NULL) && (cropBox->valid()));

    if (shouldCrop)
    {
        CGContextClipToRect(_cgContext, CGRectMake(cropBox->x(), cropBox->y(), cropBox->width(), cropBox->height()));
    }

    CGContextDrawImage(_cgContext, CGRectMake(x, y, context->width(), context->height()), cgImage);

    CGContextRestoreGState(_cgContext);

    CGImageRelease(cgImage);

    if (matrix != NULL)
    {
        CGContextConcatCTM(_cgContext, matInverted);
    }
}

The main context is taken using UIGraphicsGetCurrentContext() from a UIView class

---- EDITED --- Initialization code:

void CocoaBitmapContext::init(double width, double height, unsigned int* data)
{
    int bytesPerRow = width * 2  * VT_BITMAP_BYTES_PER_PIXEL;

    if (data == NULL)
    {
        _dataOwner = true;
        _data = (unsigned int*)malloc(bytesPerRow * height * 2);
    }
    else
    {
        _dataOwner = false;
        _data = data;
    }

    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

    _cgContext = CGBitmapContextCreate(_data,
                                       width * 2,
                                       height * 2,
                                       VT_BITMAP_BITS_PER_COMPONENT,
                                       bytesPerRow,
                                       colorSpace,
                                       kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little);

    CGContextConcatCTM(_cgContext, CGAffineTransformMakeScale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale));

    CGContextSetShouldAntialias(_cgContext, false);
    CGContextSetRGBStrokeColor(_cgContext, 0.0, 0.0, 0.0, 0.0);

    CGColorSpaceRelease(colorSpace);
}
1
The context you get with UIGraphicsGetCurrentContext() is already scaled. You should manually scale the ones you create. If possible, I suggest you use CGBitmapContextGetWidth() and CGBitmapContextGetHeight() to get the dimensions of the main context and use those dimensions to create all other context.Ahmed Mohammed
I don't think you need to change anything here, except you can use CGContextSaveGState() and CGContextRestoreGState() to restore the CTM.Ahmed Mohammed
I probably have to change something somewhere because currently it look pixelatedErik Sapir
how do you create the contexts other than the main context?Ahmed Mohammed
_cgContext = CGBitmapContextCreate(_data, width, height, VT_BITMAP_BITS_PER_COMPONENT, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little);Erik Sapir

1 Answers

4
votes

Bitmap sizes are specified in pixels where as sizes on the screen are in points. You can get the screen's scale factor using [UIScreen mainScreen].scale. That is how many pixels are in a point. For devices with retina displays, this value will be 2. You will need to scale the size of your canvas by that factor. You should also concatenate a scale transform immediately after you create the context. When you draw the images, you should still use the screen bounds as the destination rectangle (the scale transform takes care of the scaling).

CGSize canvasSize = [UIScreen mainScreen].bounds.size;
CGFloat scale = [UIScreen mainScreen].scale;
canvasSize.width *= scale;
canvasSize.height *= scale;

UIGraphicsBeginImageContext(canvasSize);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextConcatCTM(context, CGAffineTransformMakeScale(scale, scale));
...
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();

If you are going to display it in an image view, the image view should fill the screen and it's contentMode should be UIViewContentModeScaleToFill.