Skip to content

Instantly share code, notes, and snippets.

@CarlosJimenez
Last active December 18, 2015 00:08
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save CarlosJimenez/5693961 to your computer and use it in GitHub Desktop.
Save CarlosJimenez/5693961 to your computer and use it in GitHub Desktop.
Sparrow extension to support WebP files to reduce size

Sparrow modifications to support WebP

This Sparrow extension adds support for WebP in Sparrow. You can change all PNG and JPG files to WebP to reduce the size of your game.

Download WebP tools (cwebp) from: https://developers.google.com/speed/webp/download

Create and include WebP.framework in your proyect. Please consider to read http://www.ioncannon.net/programming/1483/using-webp-to-reduce-native-ios-app-size/

Examples of using WebP in https://github.com/carsonmcdonald/WebP-iOS-example

Converting Images to WebP

To create WebP versions of your images, go to Build Phases in your project. Press Add Build Phase Button, then Add Run Script. Create a Phase called "Create WebP images" and copy and paste the file WebPImages.sh

LICENSE

Copyright (c) 2013 Carlos Jimenez (Secuware)

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

//
// SPTexture.m
// Sparrow
//
// Created by Daniel Sperl on 19.06.09.
// Copyright 2011 Gamua. All rights reserved.
//
// Modified by Carlos Jimenez on 02.06.13 to support WebP file format
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the Simplified BSD License.
//
#import "SPTexture.h"
#import "SPMacros.h"
#import "SPUtils.h"
#import "SPRectangle.h"
#import "SPGLTexture.h"
#import "SPSubTexture.h"
#import "SPNSExtensions.h"
#import "SPVertexData.h"
#import "SPStage.h"
#import "SparrowClass.h"
// --- WebP support ------------------------------------------------------------------------
#define __WEBP_SUPPORT__ 1
#if __WEBP_SUPPORT__
#import "WebP/decode.h"
SPTexture *SPTextureWithContentsOfWebP(NSString *path)
{
BOOL mipmaps = NO;
BOOL pma = NO;
NSString *fullPath = [SPUtils absolutePathToFile:path];
if (!fullPath)
[NSException raise:SP_EXC_FILE_NOT_FOUND format:@"File '%@' not found", path];
float scale = [fullPath contentScaleFactor];
NSData *data = [NSData dataWithUncompressedContentsOfFile:fullPath];
WebPDecoderConfig config;
if (!WebPInitDecoderConfig(&config)) {
return nil;
}
config.output.colorspace = MODE_rgbA;
config.options.use_threads = 1;
// Decode the WebP image data into a RGBA value array.
if (WebPDecode([data bytes], [data length], &config) != VP8_STATUS_OK) {
return nil;
}
int width = config.input.width;
int height = config.input.height;
// Construct a SPGLTexture from the decoded RGBA value array.
return [[SPGLTexture alloc] initWithData: config.output.u.RGBA.rgba width:width height:height generateMipmaps:mipmaps scale:scale premultipliedAlpha:pma];
}
#endif
// --- class implementation ------------------------------------------------------------------------
@implementation SPTexture
- (id)init
{
if ([self isMemberOfClass:[SPTexture class]])
{
return [self initWithWidth:32 height:32];
}
return [super init];
}
- (id)initWithContentsOfFile:(NSString *)path
{
return [self initWithContentsOfFile:path generateMipmaps:NO];
}
- (id)initWithContentsOfFile:(NSString *)path generateMipmaps:(BOOL)mipmaps
{
#if __WEBP_SUPPORT__
if ([[path pathExtension] isEqualToString:@"webp"])
return SPTextureWithContentsOfWebP(path);
#endif
BOOL pma = [SPTexture expectedPmaValueForFile:path];
return [self initWithContentsOfFile:path generateMipmaps:mipmaps premultipliedAlpha:pma];
}
- (id)initWithContentsOfFile:(NSString *)path generateMipmaps:(BOOL)mipmaps
premultipliedAlpha:(BOOL)pma
{
NSString *fullPath = [SPUtils absolutePathToFile:path];
if (!fullPath)
[NSException raise:SP_EXC_FILE_NOT_FOUND format:@"File '%@' not found", path];
NSError *error = NULL;
NSData *data = [NSData dataWithUncompressedContentsOfFile:fullPath];
NSDictionary *options = [SPTexture optionsForPath:path mipmaps:mipmaps pma:pma];
GLKTextureInfo *info = [GLKTextureLoader textureWithContentsOfData:data
options:options error:&error];
if (!info)
{
[NSException raise:SP_EXC_FILE_INVALID
format:@"Error loading texture: %@", [error localizedDescription]];
return nil;
}
else if (mipmaps && (![SPUtils isPowerOfTwo:info.width] || ![SPUtils isPowerOfTwo:info.height])
&& glGetError() == GL_INVALID_OPERATION)
{
[NSException raise:SP_EXC_INVALID_OPERATION
format:@"Mipmapping is only supported for textures with sidelengths that "
@"are powers of two."];
}
return [[SPGLTexture alloc] initWithTextureInfo:info scale:[fullPath contentScaleFactor]
premultipliedAlpha:pma];
}
- (id)initWithWidth:(float)width height:(float)height
{
return [self initWithWidth:width height:height draw:NULL];
}
- (id)initWithWidth:(float)width height:(float)height draw:(SPTextureDrawingBlock)drawingBlock
{
return [self initWithWidth:width height:height generateMipmaps:NO draw:drawingBlock];
}
- (id)initWithWidth:(float)width height:(float)height generateMipmaps:(BOOL)mipmaps
draw:(SPTextureDrawingBlock)drawingBlock
{
return [self initWithWidth:width height:height generateMipmaps:mipmaps
scale:Sparrow.contentScaleFactor draw:drawingBlock];
}
- (id)initWithWidth:(float)width height:(float)height generateMipmaps:(BOOL)mipmaps
scale:(float)scale draw:(SPTextureDrawingBlock)drawingBlock
{
// only textures with sidelengths that are powers of 2 support all OpenGL ES features.
int legalWidth = [SPUtils nextPowerOfTwo:width * scale];
int legalHeight = [SPUtils nextPowerOfTwo:height * scale];
CGColorSpaceRef cgColorSpace = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast;
BOOL premultipliedAlpha = YES;
int bytesPerPixel = 4;
void *imageData = calloc(legalWidth * legalHeight * bytesPerPixel, 1);
CGContextRef context = CGBitmapContextCreate(imageData, legalWidth, legalHeight, 8,
bytesPerPixel * legalWidth, cgColorSpace,
bitmapInfo);
CGColorSpaceRelease(cgColorSpace);
// UIKit referential is upside down - we flip it and apply the scale factor
CGContextTranslateCTM(context, 0.0f, legalHeight);
CGContextScaleCTM(context, scale, -scale);
if (drawingBlock)
{
UIGraphicsPushContext(context);
drawingBlock(context);
UIGraphicsPopContext();
}
SPGLTexture *glTexture = [[SPGLTexture alloc] initWithData:imageData
width:legalWidth
height:legalHeight
generateMipmaps:mipmaps
scale:scale
premultipliedAlpha:premultipliedAlpha];
CGContextRelease(context);
free(imageData);
SPRectangle *region = [SPRectangle rectangleWithX:0 y:0 width:width height:height];
return [[SPTexture alloc] initWithRegion:region ofTexture:glTexture];
}
- (id)initWithContentsOfImage:(UIImage *)image
{
return [self initWithContentsOfImage:image generateMipmaps:NO];
}
- (id)initWithContentsOfImage:(UIImage *)image generateMipmaps:(BOOL)mipmaps
{
return [self initWithWidth:image.size.width height:image.size.height generateMipmaps:mipmaps
scale:image.scale draw:^(CGContextRef context)
{
[image drawAtPoint:CGPointMake(0, 0)];
}];
}
- (id)initWithRegion:(SPRectangle*)region ofTexture:(SPTexture*)texture
{
return [self initWithRegion:region frame:nil ofTexture:texture];
}
- (id)initWithRegion:(SPRectangle*)region frame:(SPRectangle *)frame ofTexture:(SPTexture*)texture
{
if (frame || region.x != 0.0f || region.width != texture.width
|| region.y != 0.0f || region.height != texture.height)
{
return [[SPSubTexture alloc] initWithRegion:region frame:frame ofTexture:texture];
}
else
{
return texture;
}
}
+ (id)textureWithContentsOfFile:(NSString *)path
{
return [[self alloc] initWithContentsOfFile:path];
}
+ (id)textureWithContentsOfFile:(NSString*)path generateMipmaps:(BOOL)mipmaps
{
return [[self alloc] initWithContentsOfFile:path generateMipmaps:mipmaps];
}
+ (id)textureWithRegion:(SPRectangle *)region ofTexture:(SPTexture *)texture
{
return [[self alloc] initWithRegion:region ofTexture:texture];
}
+ (id)textureWithWidth:(float)width height:(float)height draw:(SPTextureDrawingBlock)drawingBlock
{
return [[self alloc] initWithWidth:width height:height draw:drawingBlock];
}
+ (id)emptyTexture
{
return [[self alloc] init];
}
- (void)adjustVertexData:(SPVertexData *)vertexData atIndex:(int)index numVertices:(int)count
{
// override in subclasses
}
- (float)width
{
[NSException raise:SP_EXC_ABSTRACT_METHOD format:@"Override 'width' in subclasses."];
return 0;
}
- (float)height
{
[NSException raise:SP_EXC_ABSTRACT_METHOD format:@"Override 'height' in subclasses."];
return 0;
}
- (uint)name
{
[NSException raise:SP_EXC_ABSTRACT_METHOD format:@"Override 'name' in subclasses."];
return 0;
}
- (void)setRepeat:(BOOL)value
{
[NSException raise:SP_EXC_ABSTRACT_METHOD format:@"Override 'setRepeat:' in subclasses."];
}
- (BOOL)repeat
{
[NSException raise:SP_EXC_ABSTRACT_METHOD format:@"Override 'repeat' in subclasses."];
return NO;
}
- (SPTextureSmoothing)smoothing
{
[NSException raise:SP_EXC_ABSTRACT_METHOD format:@"Override 'smoothing' in subclasses."];
return SPTextureSmoothingBilinear;
}
- (void)setSmoothing:(SPTextureSmoothing)filter
{
[NSException raise:SP_EXC_ABSTRACT_METHOD format:@"Override 'setSmoothing' in subclasses."];
}
- (BOOL)premultipliedAlpha
{
return NO;
}
- (float)scale
{
return 1.0f;
}
- (SPRectangle *)frame
{
return nil;
}
+ (NSDictionary *)optionsForPath:(NSString *)path mipmaps:(BOOL)mipmaps pma:(BOOL)pma
{
// This is a workaround for a nasty bug in the iOS 6 simulators :|
NSDictionary *options = [NSMutableDictionary dictionaryWithObjectsAndKeys:
@(mipmaps), GLKTextureLoaderGenerateMipmaps, nil];
#if TARGET_IPHONE_SIMULATOR
NSString *osVersion = [[UIDevice currentDevice] systemVersion];
if ([osVersion isEqualToString:@"6.0"] || [osVersion isEqualToString:@"6.1"])
{
BOOL usePma = pma && [self expectedPmaValueForFile:path];
[options setValue:@(usePma) forKey:GLKTextureLoaderApplyPremultiplication];
}
#endif
return options;
}
+ (BOOL)isPVRFile:(NSString *)path
{
path = [path lowercaseString];
return [path hasSuffix:@".pvr"] || [path hasSuffix:@".pvr.gz"];
}
+ (BOOL)isPNGFile:(NSString *)path
{
path = [path lowercaseString];
return [path hasSuffix:@".png"] || [path hasSuffix:@".png.gz"];
}
+ (BOOL)isCompressedFile:(NSString *)path
{
return [[path lowercaseString] hasSuffix:@".gz"];
}
+ (BOOL)expectedPmaValueForFile:(NSString *)path
{
// PVR files typically don't use PMA.
// PNG files in the root are preprocessed by Xcode, others are not.
if ([self isPNGFile:path])
{
if ([path isAbsolutePath])
{
NSString *resourcePath = [[NSBundle appBundle] resourcePath];
return [[path stringByDeletingLastPathComponent] isEqualToString:resourcePath];
}
else return [path rangeOfString:@"/"].location == NSNotFound;
}
else return NO;
}
#pragma mark - Asynchronous Texture Loading
+ (void)loadFromFile:(NSString *)path onComplete:(SPTextureLoadingBlock)callback
{
[self loadFromFile:path generateMipmaps:NO onComplete:callback];
}
+ (void)loadFromFile:(NSString *)path generateMipmaps:(BOOL)mipmaps
onComplete:(SPTextureLoadingBlock)callback
{
BOOL pma = [SPTexture expectedPmaValueForFile:path];
[self loadFromFile:path generateMipmaps:mipmaps premultipliedAlpha:pma onComplete:callback];
}
+ (void)loadFromFile:(NSString *)path generateMipmaps:(BOOL)mipmaps premultipliedAlpha:(BOOL)pma
onComplete:(SPTextureLoadingBlock)callback;
{
NSString *fullPath = [SPUtils absolutePathToFile:path];
float actualScaleFactor = [fullPath contentScaleFactor];
if ([self isCompressedFile:path])
[NSException raise:SP_EXC_INVALID_OPERATION
format:@"Async loading of gzip-compressed files is not supported"];
if (!fullPath)
[NSException raise:SP_EXC_FILE_NOT_FOUND format:@"File '%@' not found", path];
NSDictionary *options = [SPTexture optionsForPath:path mipmaps:mipmaps pma:pma];
EAGLSharegroup *sharegroup = Sparrow.currentController.context.sharegroup;
GLKTextureLoader *loader = [[GLKTextureLoader alloc] initWithSharegroup:sharegroup];
[loader textureWithContentsOfFile:fullPath options:options queue:NULL
completionHandler:^(GLKTextureInfo *info, NSError *outError)
{
SPTexture *texture = nil;
if (!outError)
texture = [[SPGLTexture alloc] initWithTextureInfo:info scale:actualScaleFactor
premultipliedAlpha:pma];
callback(texture, outError);
}];
}
+ (void)loadFromURL:(NSURL *)url onComplete:(SPTextureLoadingBlock)callback
{
[self loadFromURL:url generateMipmaps:NO onComplete:callback];
}
+ (void)loadFromURL:(NSURL *)url generateMipmaps:(BOOL)mipmaps
onComplete:(SPTextureLoadingBlock)callback
{
float scale = [[url path] contentScaleFactor];
[self loadFromURL:url generateMipmaps:mipmaps scale:scale onComplete:callback];
}
+ (void)loadFromURL:(NSURL *)url generateMipmaps:(BOOL)mipmaps scale:(float)scale
onComplete:(SPTextureLoadingBlock)callback
{
if ([self isCompressedFile:url.path])
[NSException raise:SP_EXC_INVALID_OPERATION
format:@"Async loading of gzip-compressed files is not supported"];
NSDictionary *options = @{ GLKTextureLoaderGenerateMipmaps: @(mipmaps) };
EAGLSharegroup *sharegroup = Sparrow.currentController.context.sharegroup;
GLKTextureLoader *loader = [[GLKTextureLoader alloc] initWithSharegroup:sharegroup];
[loader textureWithContentsOfURL:url options:options queue:NULL
completionHandler:^(GLKTextureInfo *info, NSError *outError)
{
SPTexture *texture = nil;
if (!outError)
texture = [[SPGLTexture alloc] initWithTextureInfo:info scale:scale];
callback(texture, outError);
}];
}
+ (void)loadFromSuffixedURL:(NSURL *)url onComplete:(SPTextureLoadingBlock)callback
{
[self loadFromSuffixedURL:url generateMipmaps:NO onComplete:callback];
}
+ (void)loadFromSuffixedURL:(NSURL *)url generateMipmaps:(BOOL)mipmaps
onComplete:(SPTextureLoadingBlock)callback
{
float scale = Sparrow.contentScaleFactor;
NSString *suffixedString = [[url absoluteString] stringByAppendingScaleSuffixToFilename:scale];
NSURL *suffixedURL = [NSURL URLWithString:suffixedString];
[self loadFromURL:suffixedURL generateMipmaps:mipmaps scale:scale onComplete:callback];
}
@end
export PATH="/usr/local/bin:$PATH"
# convert a file to webp in same folder (if newer)
WebPFile() # $1=full path of file
{
#divide full path in $dname/$filename.$extension
dname=$(dirname "$1")
bname=$(basename "$1")
filename=${bname%.*}
extension=${bname##*.}
if test "$dname/$filename.$extension" -nt "$dname/$filename.webp"
then
echo "Creating $filename.webp..."
cwebp "$dname/$filename.$extension" -o "$dname/$filename.webp"
fi
}
# convert all jpg and png in directory to webp
WebPFolder() # $1=path
{
OIFS=$IFS
IFS=$'\n'
for fname in $(find "$1" -name "*.jpg" -type f -print); do WebPFile "$fname"; done
for fname in $(find "$1" -name "*.png" -type f -print); do WebPFile "$fname"; done
IFS=$OIFS
}
# convert all images of Scattfold example
WebPFolder "${SRCROOT}/../media/graphics/1x"
WebPFolder "${SRCROOT}/../media/graphics/2x"
WebPFolder "${SRCROOT}/../media/graphics/4x"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment