1// Copyright 2013 Howling Moon Software. All rights reserved.
2// See http://chipmunk2d.net/legal.php for more information.
3
4//
5//  ChipmunkImageSampler.m
6//  DeformableChipmunk
7//
8//  Created by Scott Lembcke on 8/26/11.
9//  Copyright 2011 __MyCompanyName__. All rights reserved.
10//
11
12#import <TargetConditionals.h>
13
14#if TARGET_OS_IPHONE == 1
15	#import <ImageIO/ImageIO.h>
16#endif
17
18#import "ChipmunkImageSampler.h"
19
20@implementation ChipmunkBitmapSampler
21
22@synthesize width = _width, height = _height, bytesPerPixel = _bytesPerPixel, component = _component, pixelData = _pixelData, outputRect = _outputRect;
23
24// Much faster than (int)floor(f)
25// Profiling showed floor() to be a sizable performance hog
26static inline int
27floor_int(cpFloat f)
28{
29	int i = (int)f;
30	return (f < 0.0f && f != i ? i - 1 : i);
31}
32
33// TODO finish this?
34//static inline cpFloat
35//SampleFunc4444(cpVect point, ChipmunkImageSampler *self)
36//{
37//	int x = (int)point.x;
38//	int y = (int)point.y - (self->_flip ? self->_height - 1 : 0);
39//
40//	int com = self->_component;
41//	int byte = self->_pixels[y*self->_stride + x*self->_bytesPerPixel + com/2];
42//	int value =
43//	return (cpFloat)(byte>>())/15.0;
44//}
45
46static cpFloat
47SampleFunc8Clamp(cpVect point, ChipmunkBitmapSampler *self)
48{
49	unsigned long w = self->_width;
50	unsigned long h = self->_height;
51
52	cpBB bb = self->_outputRect;
53	cpVect clamped = cpBBClampVect(bb, point);
54
55	unsigned long x = floor_int((w - 1)*(clamped.x - bb.l)/(bb.r - bb.l) + 0.5);
56	unsigned long y = floor_int((h - 1)*(clamped.y - bb.b)/(bb.t - bb.b) + 0.5);
57
58	if(self->_flip) y = h - 1 - y;
59
60//	printf("(%6.2f, %6.2f) -> (% 4d, % 4d) : %d\n", point.x, point.y, x, y, self->_pixels[y*self->_stride + x*self->_bytesPerPixel + self->_component]);
61	return (cpFloat)self->_pixels[y*self->_stride + x*self->_bytesPerPixel + self->_component]/255.0;
62}
63
64static cpFloat
65SampleFunc8Border(cpVect point, ChipmunkBitmapSampler *self)
66{
67	unsigned long w = self->_width;
68	unsigned long h = self->_height;
69
70	cpBB bb = self->_outputRect;
71	if(cpBBContainsVect(bb, point)){
72		unsigned long x = floor_int((w - 1)*(point.x - bb.l)/(bb.r - bb.l) + 0.5);
73		unsigned long y = floor_int((h - 1)*(point.y - bb.b)/(bb.t - bb.b) + 0.5);
74
75		if(self->_flip) y = h - 1 - y;
76
77//		printf("(%6.2f, %6.2f) -> (% 4d, % 4d)\n", point.x, point.y, x, y);
78		return (cpFloat)self->_pixels[y*self->_stride + x*self->_bytesPerPixel + self->_component]/255.0;
79	} else {
80		return self->_borderValue;
81	}
82}
83
84-(id)initWithWidth:(NSUInteger)width height:(NSUInteger)height stride:(NSUInteger)stride bytesPerPixel:(NSUInteger)bytesPerPixel component:(NSUInteger)component flip:(bool)flip pixelData:(NSData *)pixelData
85{
86	if((self = [super initWithSamplingFunction:(cpMarchSampleFunc)SampleFunc8Clamp])){
87		_width = width;
88		_height = height;
89		_stride = stride;
90
91		_bytesPerPixel = bytesPerPixel;
92		_component = component;
93
94		_flip = flip;
95		_pixelData = [pixelData retain];
96		_pixels = [pixelData bytes];
97
98		_outputRect = cpBBNew(0.5, 0.5, self.width - 0.5, self.height - 0.5);
99	}
100
101	return self;
102}
103
104
105- (void)dealloc
106{
107	[_pixelData release];
108
109	[super dealloc];
110}
111
112-(void)setBorderRepeat
113{
114	_sampleFunc = (cpMarchSampleFunc)SampleFunc8Clamp;
115}
116
117-(void)setBorderValue:(cpFloat)borderValue
118{
119	_sampleFunc = (cpMarchSampleFunc)SampleFunc8Border;
120	_borderValue = borderValue;
121}
122
123static cpBB
124BorderedBB(cpBB bb, NSUInteger width, NSUInteger height)
125{
126	cpFloat xBorder = (bb.r - bb.l)/(cpFloat)(width - 1);
127	cpFloat yBorder = (bb.t - bb.b)/(cpFloat)(height - 1);
128
129	return cpBBNew(bb.l - xBorder, bb.b - yBorder, bb.r + xBorder, bb.t + yBorder);
130}
131
132-(ChipmunkPolylineSet *)marchAllWithBorder:(bool)bordered hard:(bool)hard
133{
134	NSUInteger width = self.width;
135	NSUInteger height = self.height;
136	cpBB bb = self.outputRect;
137
138	if(bordered){
139		return [self march:BorderedBB(bb, width, height) xSamples:width+2 ySamples:height+2 hard:hard];
140	} else {
141		return [self march:bb xSamples:width ySamples:height hard:hard];
142	}
143}
144
145@end
146
147
148
149@implementation ChipmunkCGContextSampler
150
151@synthesize context = _context;
152
153-(NSMutableData *)pixelData {return (NSMutableData *)super.pixelData;}
154
155-(id)initWithWidth:(unsigned long)width height:(unsigned long)height colorSpace:(CGColorSpaceRef)colorSpace bitmapInfo:(CGBitmapInfo)bitmapInfo component:(NSUInteger)component
156{
157	// Need to create a context to get info about the context.
158	// If you let the context allocate it's own memory it seems to move it around. O_o
159	CGContextRef temp = CGBitmapContextCreate(NULL, width, height, 8, 0, colorSpace, bitmapInfo);
160	cpAssertHard(temp, "Failed to create temporary CGBitmapContext");
161
162	unsigned long bpc = CGBitmapContextGetBitsPerComponent(temp);
163	unsigned long bpp = CGBitmapContextGetBitsPerPixel(temp)/8;
164	cpAssertHard(bpc == 8, "Cannot handle non-8bit-per-pixel bitmap data!");
165
166	CGContextRelease(temp);
167
168	unsigned long stride = width*bpp;
169	NSMutableData *pixelData = [NSMutableData dataWithLength:stride*height];
170	_context = CGBitmapContextCreate([pixelData mutableBytes], width, height, bpc, stride, colorSpace, bitmapInfo);
171
172	return [self initWithWidth:width height:height stride:stride bytesPerPixel:bpp component:component flip:TRUE pixelData:pixelData];
173}
174
175-(void)dealloc
176{
177	CGContextRelease(_context);
178
179	[super dealloc];
180}
181
182@end
183
184
185
186@implementation ChipmunkImageSampler
187
188+(CGImageRef)loadImage:(NSURL *)url
189{
190	CGImageSourceRef image_source = CGImageSourceCreateWithURL((CFURLRef)url, NULL);
191	CGImageRef image = CGImageSourceCreateImageAtIndex(image_source, 0, NULL);
192	cpAssertHard(image, "Image %s could not be loaded.", [[url description] UTF8String]);
193
194	CFRelease(image_source);
195	return image;
196}
197
198-(id)initWithImage:(CGImageRef)image isMask:(bool)isMask contextWidth:(NSUInteger)width contextHeight:(NSUInteger)height
199{
200	if(width == 0) width = CGImageGetWidth(image);
201	if(height == 0)  height = CGImageGetHeight(image);
202
203	CGColorSpaceRef colorSpace = (isMask ? CGColorSpaceCreateDeviceGray() : NULL);
204	CGBitmapInfo bitmapInfo = (CGBitmapInfo)(isMask ? kCGImageAlphaNone : kCGImageAlphaOnly);
205
206	if((self = [super initWithWidth:width height:height colorSpace:colorSpace bitmapInfo:bitmapInfo component:0])){
207		CGContextDrawImage(self.context, CGRectMake(0, 0, width, height), image);
208	}
209
210	CGColorSpaceRelease(colorSpace);
211
212	return self;
213}
214
215-(id)initWithImageFile:(NSURL *)url isMask:(bool)isMask
216{
217	CGImageRef image = [[self class] loadImage:url];
218	unsigned long width = CGImageGetWidth(image);
219	unsigned long height = CGImageGetHeight(image);
220
221	self = [self initWithImage:image isMask:isMask contextWidth:width contextHeight:height];
222
223	CGImageRelease(image);
224
225	return self;
226}
227
228+(ChipmunkImageSampler *)samplerWithImageFile:(NSURL *)url isMask:(bool)isMask
229{
230	return [[[self alloc] initWithImageFile:url	isMask:isMask] autorelease];
231}
232
233@end
234