Use this as freely as you wish, I don't really need you to credit me or anything but if you can try it out and improve upon the code. I'd love to know about it.
Oh and to use any setPixel, fill, copy. Remember to call apply to actually update the texture.
I would also love to figure out how to retain the texture data for a PVR image if anyone can point me in the right direction...
MutableTexture2D.h
///
// MutableTexture2D extends cocos2D Texture2D.h
// Created by Lam Hoang Pham.
//
// Allows for modifications of the texture data. If you load PVR's then you are
// out of luck. Supported pixelFormats will allow access to bitmap data.
//
// Support for RGBA_4_4_4_4 and RGBA_5_5_5_1 was copied from:
// https://devforums.apple.com/message/37855#37855 by a1studmuffin
//
///
#import <UIKit/UIKit.h>
#import <OpenGLES/ES1/gl.h>
#import "Texture2D.h"
#import "cocos2d.h"
@interface MutableTexture2D : Texture2D
{
void *data_;
bool dirty_;
}
// Returns the maximum allowed texture size
+ (int) maxTextureSize;
@end
@interface MutableTexture2D(Image)
///
// Create a texture with an image
///
- (id) initWithImage:(UIImage*) image;
@end
@interface MutableTexture2D (MutableTexture)
///
// Create a blank texture with canvas size and default pixel format
///
+ (id) textureWithSize:(CGSize) size;
///
// Create a blank texture with canvas size and chosen pixel format
///
+ (id) textureWithSize:(CGSize) size pixelFormat:(Texture2DPixelFormat) pixelFormat;
///
// Create a blank texture with canvas size and default pixel format
///
- (id) initWithSize:(CGSize) size pixelFormat:(Texture2DPixelFormat) pixelFormat;
///
// @param pt is a point to get a pixel (0,0) is top-left to (width,height) bottom-right
// @returns a ccColor4B which is a colour, otherwise it returns Texture2DPixelClear
///
- (ccColor4B) pixelAt:(CGPoint) pt;
///
// @param pt is a point to get a pixel (0,0) is top-left to (width,height) bottom-right
// @param c is a ccColor4B which is a colour.
// @returns if a pixel was set
// Remember to call apply to actually update the texture canvas.
///
- (BOOL) setPixelAt:(CGPoint) pt rgba:(ccColor4B) c;
///
// Fill with specified colour
///
- (void) fill:(ccColor4B) c;
///
// @param textureToCopy is the texture image to copy over
// @param offset also offset's the texture image
///
- (void) copy:(MutableTexture2D*) textureToCopy offset:(CGPoint) offset;
///
// apply actually updates the texture with any new data we added.
///
- (void) apply;
@end
///
// Fast find for powers of 2
///
extern bool IsPow2(uint v);
///
// Fast round to nearest power of 2 for 32-bit int's.
///
extern uint RoundToNearestPow2(uint v);
MutableTexture2D.m
#import <OpenGLES/ES1/glext.h>
#import "MutableTexture2D.h"
///
// Fast find for powers of 2
///
bool IsPow2(uint v){
return (v > 1) && ((v & (v - 1)) == 0);
}
///
// Fast round to nearest power of 2 for 32-bit int's.
///
uint RoundToNearestPow2(uint v){
//if(v < 32) return 32;
if(v <= 1)return 2;
v--;
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
v++;
return v;
}
@implementation MutableTexture2D
+ (int) maxTextureSize {
return 1024;
}
- (id) initWithData:(const void*)data pixelFormat:(Texture2DPixelFormat)pixelFormat pixelsWide:(NSUInteger)width pixelsHigh:(NSUInteger)height contentSize:(CGSize)size
{
if((self = [super initWithData:data pixelFormat:pixelFormat pixelsWide:width pixelsHigh:height contentSize:size])) {
data_ = NULL;
dirty_ = false;
}
return self;
}
- (void) dealloc
{
if(data_){
free(data_);
data_ = NULL;
}
[super dealloc];
}
@end
@implementation MutableTexture2D(Image)
- (id) initWithImage:(UIImage *)uiImage
{
NSUInteger width,
height,
i;
CGContextRef context = nil;
void* data = nil;;
CGColorSpaceRef colorSpace;
void* tempData;
unsigned int* inPixel32;
unsigned short* outPixel16;
BOOL hasAlpha;
CGImageAlphaInfo info;
CGAffineTransform transform;
CGSize imageSize;
Texture2DPixelFormat pixelFormat;
CGImageRef image;
BOOL sizeToFit = NO;
image = [uiImage CGImage];
if(image == NULL) {
[self release];
NSLog(@"Image is Null");
return nil;
}
info = CGImageGetAlphaInfo(image);
hasAlpha = ((info == kCGImageAlphaPremultipliedLast) || (info == kCGImageAlphaPremultipliedFirst) || (info == kCGImageAlphaLast) || (info == kCGImageAlphaFirst) ? YES : NO);
size_t bpp = CGImageGetBitsPerComponent(image);
if(CGImageGetColorSpace(image)) {
if(hasAlpha || bpp >= 8)
pixelFormat = [[self class] defaultAlphaPixelFormat];
else
pixelFormat = kTexture2DPixelFormat_RGB565;
} else //NOTE: No colorspace means a mask image
pixelFormat = kTexture2DPixelFormat_A8;
imageSize = CGSizeMake(CGImageGetWidth(image), CGImageGetHeight(image));
transform = CGAffineTransformIdentity;
width = imageSize.width;
if((width != 1) && (width & (width - 1))) {
i = 2;
while((sizeToFit ? 2 * i : i) < width)
i *= 2;
width = i;
}
height = imageSize.height;
if((height != 1) && (height & (height - 1))) {
i = 2;
while((sizeToFit ? 2 * i : i) < height)
i *= 2;
height = i;
}
// iPhone 3GS supports 2048 textures, so the maxtexturesize should be a variable, not a hardcoded value
NSAssert2( (width <= [[self class] maxTextureSize]) && (height <= [[self class] maxTextureSize]), @"Image is bigger than the supported %d x %d", [[self class] maxTextureSize], [[self class] maxTextureSize]);
// while((width > kMaxTextureSize) || (height > kMaxTextureSize)) {
// width /= 2;
// height /= 2;
// transform = CGAffineTransformScale(transform, 0.5f, 0.5f);
// imageSize.width *= 0.5f;
// imageSize.height *= 0.5f;
// }
// Create the bitmap graphics context
switch(pixelFormat) {
case kTexture2DPixelFormat_RGBA8888:
case kTexture2DPixelFormat_RGBA4444:
case kTexture2DPixelFormat_RGB5A1:
colorSpace = CGColorSpaceCreateDeviceRGB();
data = malloc(height * width * 4);
context = CGBitmapContextCreate(data, width, height, 8, 4 * width, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);
break;
case kTexture2DPixelFormat_RGB565:
colorSpace = CGColorSpaceCreateDeviceRGB();
data = malloc(height * width * 4);
context = CGBitmapContextCreate(data, width, height, 8, 4 * width, colorSpace, kCGImageAlphaNoneSkipLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);
break;
case kTexture2DPixelFormat_A8:
data = malloc(height * width);
context = CGBitmapContextCreate(data, width, height, 8, width, NULL, kCGImageAlphaOnly);
break;
default:
[NSException raise:NSInternalInconsistencyException format:@"Invalid pixel format"];
}
CGContextClearRect(context, CGRectMake(0, 0, width, height));
CGContextTranslateCTM(context, 0, height - imageSize.height);
if(!CGAffineTransformIsIdentity(transform))
CGContextConcatCTM(context, transform);
CGContextDrawImage(context, CGRectMake(0, 0, CGImageGetWidth(image), CGImageGetHeight(image)), image);
// Repack the pixel data into the right format
if(pixelFormat == kTexture2DPixelFormat_RGB565) {
//Convert "RRRRRRRRRGGGGGGGGBBBBBBBBAAAAAAAA" to "RRRRRGGGGGGBBBBB"
tempData = malloc(height * width * 2);
inPixel32 = (unsigned int*)data;
outPixel16 = (unsigned short*)tempData;
for(i = 0; i < width * height; ++i, ++inPixel32)
*outPixel16++ = ((((*inPixel32 >> 0) & 0xFF) >> 3) << 11) | ((((*inPixel32 >> 8) & 0xFF) >> 2) << 5) | ((((*inPixel32 >> 16) & 0xFF) >> 3) << 0);
free(data);
data = tempData;
}
else if (pixelFormat == kTexture2DPixelFormat_RGBA4444) {
//Convert "RRRRRRRRRGGGGGGGGBBBBBBBBAAAAAAAA" to "RRRRGGGGBBBBAAAA"
tempData = malloc(height * width * 2);
inPixel32 = (unsigned int*)data;
outPixel16 = (unsigned short*)tempData;
for(i = 0; i < width * height; ++i, ++inPixel32)
*outPixel16++ =
((((*inPixel32 >> 0) & 0xFF) >> 4) << 12) | // R
((((*inPixel32 >> 8) & 0xFF) >> 4) << 8) | // G
((((*inPixel32 >> 16) & 0xFF) >> 4) << 4) | // B
((((*inPixel32 >> 24) & 0xFF) >> 4) << 0); // A
free(data);
data = tempData;
}
else if (pixelFormat == kTexture2DPixelFormat_RGB5A1) {
//Convert "RRRRRRRRRGGGGGGGGBBBBBBBBAAAAAAAA" to "RRRRRGGGGGBBBBBA"
tempData = malloc(height * width * 2);
inPixel32 = (unsigned int*)data;
outPixel16 = (unsigned short*)tempData;
for(i = 0; i < width * height; ++i, ++inPixel32)
*outPixel16++ =
((((*inPixel32 >> 0) & 0xFF) >> 3) << 11) | // R
((((*inPixel32 >> 8) & 0xFF) >> 3) << 6) | // G
((((*inPixel32 >> 16) & 0xFF) >> 3) << 1) | // B
((((*inPixel32 >> 24) & 0xFF) >> 7) << 0); // A
free(data);
data = tempData;
}
self = [self initWithData:data pixelFormat:pixelFormat pixelsWide:width pixelsHigh:height contentSize:imageSize];
// should be after calling super init
_hasPremultipliedAlpha = (info == kCGImageAlphaPremultipliedLast || info == kCGImageAlphaPremultipliedFirst);
CGContextRelease(context);
// This is the only change =/ but we want to keep the data for mutable methods
data_ = data;
return self;
}
@end
@implementation MutableTexture2D (MutableTexture)
+ (id) textureWithSize:(CGSize) size {
return [[[self alloc] initWithSize:size pixelFormat:[[self class] defaultAlphaPixelFormat]] autorelease];
}
+ (id) textureWithSize:(CGSize) size pixelFormat:(Texture2DPixelFormat) pixelFormat {
return [[[self alloc] initWithSize:size pixelFormat:pixelFormat] autorelease];
}
- (id) initWithSize:(CGSize) size pixelFormat:(Texture2DPixelFormat) pixelFormat {
if((self = [super init])){
_format = pixelFormat;
_size = size;
_width = size.width;
if(!IsPow2(_width))
_width = RoundToNearestPow2(_width);
_height = size.height;
if(!IsPow2(_height))
_height = RoundToNearestPow2(_height);
int dataSize = 0;
switch (_format) {
case kTexture2DPixelFormat_RGBA8888:
dataSize = _width * _height * sizeof(int);
break;
case kTexture2DPixelFormat_RGBA4444:
case kTexture2DPixelFormat_RGB5A1:
case kTexture2DPixelFormat_RGB565:
dataSize = _width * _height * sizeof(short);
break;
case kTexture2DPixelFormat_A8:
dataSize = _width * _height;
break;
default:
break;
}
_maxS = _size.width / (float)_width;
_maxT = _size.height / (float)_height;
_hasPremultipliedAlpha = NO;
data_ = calloc(dataSize, 1);
NSAssert(data_, @"Low Memory, could not allocate Texture Data");
glGenTextures(1, &_name);
glBindTexture(GL_TEXTURE_2D, _name);
[self setAntiAliasTexParameters];
switch(_format)
{
case kTexture2DPixelFormat_RGBA8888:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _width, _height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data_);
break;
case kTexture2DPixelFormat_RGBA4444:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _width, _height, 0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, data_);
break;
case kTexture2DPixelFormat_RGB5A1:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _width, _height, 0, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, data_);
break;
case kTexture2DPixelFormat_RGB565:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, _width, _height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, data_);
break;
case kTexture2DPixelFormat_A8:
glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, _width, _height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, data_);
break;
default:
[NSException raise:NSInternalInconsistencyException format:@""];
}
}
return self;
}
- (ccColor4B) pixelAt:(CGPoint) pt {
ccColor4B c = {0, 0, 0, 0};
if(!data_) return c;
if(pt.x < 0 || pt.y < 0) return c;
if(pt.x >= _size.width || pt.y >= _size.height) return c;
uint x = pt.x, y = pt.y;
if(_format == kTexture2DPixelFormat_RGBA8888){
uint *pixel = data_;
pixel = pixel + (y * _width) + x;
c.r = *pixel & 0xff;
c.g = (*pixel >> 8) & 0xff;
c.b = (*pixel >> 16) & 0xff;
c.a = (*pixel >> 24) & 0xff;
} else if(_format == kTexture2DPixelFormat_RGBA4444){
GLushort *pixel = data_;
pixel = pixel + (y * _width) + x;
c.r = *pixel & 0xf;
c.g = (*pixel >> 4) & 0xf;
c.b = (*pixel >> 8) & 0xf;
c.a = (*pixel >> 12) & 0xf;
} else if(_format == kTexture2DPixelFormat_RGB5A1){
GLushort *pixel = data_;
pixel = pixel + (y * _width) + x;
c.r = *pixel & 0x1f;
c.g = (*pixel >> 5) & 0x1f;
c.b = (*pixel >> 10) & 0x1f;
c.a = (*pixel >> 15) & 0x1;
} else if(_format == kTexture2DPixelFormat_RGB565){
GLushort *pixel = data_;
pixel = pixel + (y * _width) + x;
c.r = *pixel & 0x1f;
c.g = (*pixel >> 5) & 0x3f;
c.b = (*pixel >> 11) & 0x1f;
c.a = 255;
} else if(_format == kTexture2DPixelFormat_A8){
GLubyte *pixel = data_;
c.a = pixel[(y * _width) + x];
}
return c;
}
- (BOOL) setPixelAt:(CGPoint) pt rgba:(ccColor4B) c {
if(!data_)return NO;
if(pt.x < 0 || pt.y < 0) return NO;
if(pt.x >= _size.width || pt.y >= _size.height) return NO;
uint x = pt.x, y = pt.y;
dirty_ = true;
// Shifted bit placement based on little-endian, let's make this more
// portable =/
if(_format == kTexture2DPixelFormat_RGBA8888){
uint *pixel = data_;
pixel[(y * _width) + x] = (c.a << 24) | (c.b << 16) | (c.g << 8) | c.r;
} else if(_format == kTexture2DPixelFormat_RGBA4444){
GLushort *pixel = data_;
pixel = pixel + (y * _width) + x;
*pixel = ((c.a >> 4) << 12) | ((c.b >> 4) << 8) | ((c.g >> 4) << 4) | (c.r >> 4);
} else if(_format == kTexture2DPixelFormat_RGB5A1){
GLushort *pixel = data_;
pixel = pixel + (y * _width) + x;
*pixel = (c.r >> 3) | ((c.g >> 3) << 5) | ((c.b >> 3) << 10) | ((c.a >> 7) << 15);
} else if(_format == kTexture2DPixelFormat_RGB565){
GLushort *pixel = data_;
pixel = pixel + (y * _width) + x;
*pixel = ((c.b >> 3) << 11) | ((c.g >> 2) << 5) | (c.r >> 3);
} else if(_format == kTexture2DPixelFormat_A8){
GLubyte *pixel = data_;
pixel[(y * _width) + x] = c.a;
} else {
dirty_ = false;
return NO;
}
return YES;
}
- (void) fill:(ccColor4B) p {
for(int r = 0; r < _size.height;++r){
for(int c = 0; c < _size.width; ++c){
[self setPixelAt:CGPointMake(c, r) rgba:p];
}
}
}
- (void) copy:(MutableTexture2D*) textureToCopy offset:(CGPoint) offset {
for(int r = 0; r < _size.height;++r){
for(int c = 0; c < _size.width; ++c){
[self setPixelAt:CGPointMake(c + offset.x, r + offset.y) rgba:[textureToCopy pixelAt:CGPointMake(c, r)]];
}
}
}
- (void) apply {
if(!dirty_) return;
if(!data_) return;
glBindTexture(GL_TEXTURE_2D, _name);
switch(_format)
{
case kTexture2DPixelFormat_RGBA8888:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _width, _height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data_);
break;
case kTexture2DPixelFormat_RGBA4444:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _width, _height, 0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, data_);
break;
case kTexture2DPixelFormat_RGB5A1:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _width, _height, 0, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, data_);
break;
case kTexture2DPixelFormat_RGB565:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, _width, _height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, data_);
break;
case kTexture2DPixelFormat_A8:
glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, _width, _height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, data_);
break;
default:
[NSException raise:NSInternalInconsistencyException format:@""];
}
dirty_ = false;
}
@end