I wrote a gradient generating function as a category on SKTexture. It works well on 1x screens but retina renders the texture too big, double width and double height ie wrong scale. I have been trying to get it right by changing between pixels and points, but can't get it right. Can someone help me please?
+(SKTexture*)gradientWithSize:(const CGSize)SIZE colors:(NSArray*)colors {
// Hopefully this function would be platform independent one day.
// Correct way to find scale?
DLog(@"backingScaleFactor: %f", [[NSScreen mainScreen] backingScaleFactor]);
const CGFloat SCALE = [[NSScreen mainScreen] backingScaleFactor];
//const size_t WIDTH_PIXELS = SIZE.width * SCALE;
//const size_t HEIGHT_PIXELS = SIZE.height * SCALE;
CGContextRef cgcontextref = MyCreateBitmapContext(SIZE.width, SIZE.height, SCALE);
NSAssert(cgcontextref != NULL, @"Failed creating context!");
// CGBitmapContextCreate(
// NULL, // let the OS handle the memory
// WIDTH_PIXELS,
// HEIGHT_PIXELS,
CAGradientLayer* gradient = CAGradientLayer.layer;
//gradient.contentsScale = SCALE;
gradient.frame = CGRectMake(0, 0, SIZE.width, SIZE.height);
NSMutableArray* convertedcolors = [NSMutableArray array];
for (SKColor* skcolor in colors) {
[convertedcolors addObject:(id)skcolor.CGColor];
}
gradient.colors = convertedcolors;
[gradient renderInContext:cgcontextref];
CGImageRef imageref = CGBitmapContextCreateImage(cgcontextref);
DLog(@"imageref pixel size: %zu %zu", CGImageGetWidth(imageref), CGImageGetHeight(imageref));
SKTexture* texture1 = [SKTexture textureWithCGImage:imageref];
DLog(@"size of gradient texture: %@", NSStringFromSize(texture1.size));
CGImageRelease(imageref);
CGContextRelease(cgcontextref);
return texture1;
}
CGContextRef MyCreateBitmapContext(const size_t POINTS_W, const size_t POINTS_H, const CGFloat SCALE) {
CGContextRef context = NULL;
CGColorSpaceRef colorSpace;
void * bitmapData;
//int bitmapByteCount;
size_t bitmapBytesPerRow;
const size_t PIXELS_W = POINTS_W * SCALE;
const size_t PIXELS_H = POINTS_H * SCALE;
bitmapBytesPerRow = (PIXELS_W * 4);// 1
//bitmapByteCount = (bitmapBytesPerRow * pixelsHigh);
colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);// 2
bitmapData = NULL;
#define kBitmapInfo kCGImageAlphaPremultipliedLast
//#define kBitmapInfo kCGImageAlphaPremultipliedFirst
//#define kBitmapInfo kCGImageAlphaNoneSkipFirst
// According to http://stackoverflow.com/a/18921840/129202 it should be safe to just cast
CGBitmapInfo bitmapinfo = (CGBitmapInfo)kBitmapInfo; //kCGImageAlphaNoneSkipFirst; //0; //kCGBitmapAlphaInfoMask; //kCGImageAlphaNone; //kCGImageAlphaNoneSkipFirst;
context = CGBitmapContextCreate (bitmapData,// 4
PIXELS_W,
PIXELS_H,
8, // bits per component
bitmapBytesPerRow,
colorSpace,
bitmapinfo
);
if (context == NULL) {
free (bitmapData);// 5
fprintf (stderr, "Context not created!");
return NULL;
}
CGColorSpaceRelease( colorSpace );// 6
// TEST!!
// CGContextClipToRect(context, CGRectMake(0, 0, POINTS_W, POINTS_H));
CGContextClipToRect(context, CGRectMake(0, 0, PIXELS_W, PIXELS_H));
CGContextScaleCTM(context, SCALE, SCALE);
return context;// 7
}
So, to some sum it up, I expect to call +(SKTexture*)gradientWithSize:(const CGSize)SIZE colors:(NSArray*)colors
using points for SIZE.
Updated the code as below based on LearnCocos2D's suggestion. When to scale and not to is confusing (because in the docs,
CGBitmapContextCreate
explicitly takes width and height in PIXELS) but this seems to have the size right also on retina screens. Still I don't know if the rendered resolution is really retina - but this is very hard to confirm using eye because after all this is a gradient... Still I'm using the same context creating functions for a texture repeater (to enable to use colorWithPatternImage with Sprite Kit). Maybe I have the time later to try a detailed texture repeated using this new version... Also leavinggradient.contentsScale = SCALE;
on or off doesn't make a visible difference.Results below. The left is just a yellow rectangle created using SKSpriteNode. The right is a rectangle created by using the texture generated from the above function. The two rectangles are at the same position and have same point sizes, just different anchorPoints.
1x resolution
2x resolution (retina)