1//  PXImage.m
2//  Pixen
3//
4//  Created by Joe Osborn on Thu Sep 11 2003.
5//  Copyright (c) 2003 Open Sword Group. All rights reserved.
6//
7
8#import "PXImage.h"
9#import "PXPixel.h"
10#import "KTMutableMatrix.h"
11
12extern BOOL isTiling;
13
14@implementation PXImage
15
16- initWithSize:(NSSize)aSize
17{
18    [super init];
19	width = aSize.width;
20	height = aSize.height;
21    pixelsByColor = [[NSMutableDictionary alloc] initWithCapacity:128];
22    pixelsByPosition = [[KTMutableMatrix matrixWithCapacity:512*512 cuboidBounds:(unsigned)(width), (unsigned)(height), 0, 0] retain];
23	rects = malloc(sizeof(NSRect) * (height + 2));
24	colors = malloc(sizeof(NSColor *) * (height + 2));
25    return self;
26}
27
28- (NSSize)size
29{
30	return NSMakeSize(width, height);
31}
32
33- (void)setSize:(NSSize)newSize withOrigin:(NSPoint)origin backgroundColor:(NSColor *)color
34{
35	id newPixelsByPosition = [[KTMutableMatrix matrixWithCapacity:512*512 cuboidBounds:(unsigned)(newSize.width), (unsigned)(newSize.height), 0, 0] retain];
36	unsigned int i, j;
37	unsigned int newWidth = newSize.width, newHeight = newSize.height;
38	unsigned int originX = origin.x, originY = origin.y;
39	for(i = 0; i < newWidth; i++)
40	{
41		for(j = 0; j < newHeight; j++)
42		{
43			if (i < originX || j < originY || i >= width + originX || j >= height + originY) {
44				[newPixelsByPosition setObject:[PXPixel withColor:color] atCoordinates:i, j];
45			} else {
46				[newPixelsByPosition setObject:[self pixelAtPoint:NSMakePoint(i - originX, j - originY)] atCoordinates:i, j];
47			}
48		}
49	}
50	width = newWidth;
51	height = newHeight;
52	[pixelsByPosition release];
53	pixelsByPosition = newPixelsByPosition;
54	free(rects);
55	free(colors);
56	rects = malloc(sizeof(NSRect) * (height + 2));
57	colors = malloc(sizeof(NSColor *) * (height + 2));
58}
59
60- (void)setSize:(NSSize)newSize
61{
62	[self setSize:newSize withOrigin:NSMakePoint(0,0) backgroundColor:[NSColor clearColor]];
63}
64
65- (void)dealloc
66{
67    [pixelsByColor release];
68    [pixelsByPosition release];
69	free(rects);
70	free(colors);
71    [super dealloc];
72}
73
74- (BOOL)containsPoint:(NSPoint)point
75{
76	return (isTiling ? YES : NSPointInRect(point, NSMakeRect(0, 0, width, height)));
77}
78
79- (NSColor *)colorAtX:(unsigned int)x y:(unsigned int)y
80{
81	return [[self pixelAtX:x y:y] color];
82}
83
84- (NSColor *)colorAtPoint:(NSPoint)aPoint
85{
86	//if(![self containsPoint:aPoint]) { return nil; }
87	return [[self pixelAtPoint:aPoint] color];
88}
89
90- pixelOfColor:aColor
91{
92	if(aColor == nil) { return nil; }
93    id pixel = [pixelsByColor objectForKey:aColor];
94    if(pixel == nil)
95	{
96		[pixelsByColor setObject:[PXPixel withColor:aColor] forKey:aColor];
97		pixel = [pixelsByColor objectForKey:aColor];
98		[[NSNotificationCenter defaultCenter] postNotificationName:@"PXImageColorAddedNotification" object:self userInfo:[NSDictionary dictionaryWithObject:aColor forKey:@"color"]];
99	}
100    return pixel;
101}
102
103- (NSPoint)correct:(NSPoint)aPoint
104{
105	NSPoint corrected = aPoint;
106	while(corrected.x < 0)
107	{
108		corrected.x += width;
109	}
110	while(corrected.x >= width)
111	{
112		corrected.x -= width;
113	}
114	while(corrected.y < 0)
115	{
116		corrected.y += height;
117	}
118	while(corrected.y >= height)
119	{
120		corrected.y -= height;
121	}
122	return corrected;
123}
124
125- (unsigned int)correctX:(unsigned int)x
126{
127	while(x < 0)
128	{
129		x += width;
130	}
131	while(x >= width)
132	{
133		x -= width;
134	}
135	return x;
136}
137
138- (unsigned int)correctY:(unsigned int)y
139{
140	while(y < 0)
141	{
142		y += height;
143	}
144	while(y >= height)
145	{
146		y -= height;
147	}
148	return y;
149}
150
151- _pixelAtX:(unsigned int)x y:(unsigned int)y
152{
153	return [pixelsByPosition objectAtCoordinates:x, y];
154}
155
156- _pixelAtPoint:(NSPoint)aPoint
157{
158//	id pixel = [pixelsByPosition objectAtCoordinates:(unsigned)(aPoint.x), (unsigned)(aPoint.y)];
159//	if ([[pixel color] alphaComponent] == 0) {
160//		return nil;
161//	}
162	return [self _pixelAtX:aPoint.x y:aPoint.y];
163}
164
165- pixelAtX:(unsigned int)x y:(unsigned int)y
166{
167	return [self _pixelAtX:[self correctX:x] y:[self correctY:y]];
168}
169
170- pixelAtPoint:(NSPoint)aPoint
171{
172	NSPoint corrected = [self correct:aPoint];
173    return [self _pixelAtPoint:corrected];
174}
175
176- (void)setPixel:aPixel atPoint:(NSPoint)aPoint
177{
178	NSPoint corrected = [self correct:aPoint];
179    [pixelsByPosition setObject:aPixel atCoordinates:(unsigned)(corrected.x), (unsigned)(corrected.y)];
180}
181
182- (void)setPixel:aPixel atPoints:(NSArray *)points
183{
184    id enumerator = [points objectEnumerator];
185    id current;
186    while ( ( current = [enumerator nextObject] ) )
187    {
188        [self setPixel:aPixel atPoint:[current pointValue]];
189    }
190}
191
192- (void)setColor:aColor atPoint:(NSPoint)aPoint
193{
194    [self setPixel:[self pixelOfColor:aColor] atPoint:aPoint];
195}
196
197- (void)setColor:(NSColor *)aColor atPoints:(NSArray *)points
198{
199    [self setPixel:[self pixelOfColor:aColor] atPoints:points];
200}
201
202- (void)replacePixelsOfColor:oldColor withColor:newColor
203{
204	id pixel = [self pixelOfColor:oldColor];
205	if((pixel == nil) || ([oldColor isEqual:newColor])) { return; }
206	[pixel setColor:newColor];
207	[pixelsByColor setObject:pixel forKey:newColor];
208	[pixelsByColor removeObjectForKey:oldColor];
209}
210
211- (void)translateXBy:(float)amountX yBy:(float)amountY
212{
213	id newMatrix = [KTMutableMatrix matrixWithCapacity:512*512 cuboidBounds:(unsigned)(width), (unsigned)(height), 0, 0];
214	int i, j;
215	for(i = 0; i < width; i++)
216	{
217		for(j = 0; j < height; j++)
218		{
219			float newX = i + amountX, newY = j + amountY;
220			if(!(newX < 0 || newX >= width || newY < 0 || newY >= height))
221			{
222				[newMatrix setObject:[self pixelAtPoint:NSMakePoint(i, j)] atCoordinates:(unsigned)newX, (unsigned)newY];
223			}
224		}
225	}
226	[pixelsByPosition release];
227	pixelsByPosition = [newMatrix retain];
228}
229
230
231- (void)flipHorizontally
232{
233	id newMatrix = [KTMutableMatrix matrixWithCapacity:512*512 cuboidBounds:(unsigned)(width), (unsigned)(height), 0, 0];
234	int i, j;
235	for(i = 0; i < width; i++)
236	{
237		for(j = 0; j < height; j++)
238		{
239			[newMatrix setObject:[self pixelAtPoint:NSMakePoint(width - i - 1, j)] atCoordinates:(unsigned)i, (unsigned)j];
240		}
241	}
242	[pixelsByPosition release];
243	pixelsByPosition = [newMatrix retain];
244}
245
246- (void)flipVertically
247{
248	id newMatrix = [KTMutableMatrix matrixWithCapacity:512*512 cuboidBounds:(unsigned)(width), (unsigned)(height), 0, 0];
249	int i, j;
250	for(i = 0; i < width; i++)
251	{
252		for(j = 0; j < height; j++)
253		{
254			[newMatrix setObject:[self pixelAtPoint:NSMakePoint(i, height - j - 1)] atCoordinates:(unsigned)i, (unsigned)j];
255		}
256	}
257	[pixelsByPosition release];
258	pixelsByPosition = [newMatrix retain];
259}
260
261- (void)drawRect:(NSRect)rect withOpacity:(double)anOpacity fixBug:(BOOL)fixBug
262{
263	unsigned int startX = MAX(rect.origin.x-1, 0), startY = MAX(rect.origin.y-1, 0);
264	unsigned int offsetX = startX+rect.size.width+1, offsetY = startY+rect.size.height+1;
265    unsigned int i, j;
266	unsigned int count = 0;
267	SEL colorAtXYselector = @selector(colorAtX:y:);
268	IMP colorAtXY = [self methodForSelector:colorAtXYselector];
269
270    for(i = startX; (i < offsetX) && (i < width); i++)
271    {
272        for(j = startY; (j < offsetY) && (j < height); j++)
273        {
274			rects[count] = NSMakeRect(i,j,1,1);
275			colors[count] = colorAtXY(self, colorAtXYselector, i, j);
276			if (colors[count] == nil)
277			{
278				colors[count] = [NSColor clearColor];
279			}
280			if (anOpacity != 1)
281			{
282				colors[count] = [colors[count] colorWithAlphaComponent:[colors[count] alphaComponent] * anOpacity];
283			}
284			count++;
285        }
286		NSRectFillListWithColorsUsingOperation(rects, colors, count, fixBug ? NSCompositeDestinationOver : NSCompositeSourceAtop);
287		count = 0;
288    }
289}
290
291- description
292{
293	int i, j;
294	NSString * result = @"";
295	for (i = 0; i < width; i++)
296	{
297		for (j = 0; j < height; j++)
298		{
299			result = [result stringByAppendingFormat:@"(%d,%d): %@, ", i, j, [self colorAtPoint:NSMakePoint(i,j)]];
300		}
301	}
302	return result;
303}
304
305- (void)compositeUnder:anImage
306{
307	NSImage * compositedImage = [[[NSImage alloc] initWithSize:[self size]] autorelease];
308	NSRect fullRect = NSMakeRect(0,0,width,height);
309	NSPoint point;
310	int i, j;
311	id pool;
312
313	[compositedImage lockFocus];
314	[anImage drawRect:fullRect withOpacity:1 fixBug:YES];
315	[self drawRect:fullRect withOpacity:1 fixBug:YES];
316    for(i = 0; i < width; i++)
317    {
318        pool = [[NSAutoreleasePool alloc] init];
319        for(j = 0; j < height; j++)
320        {
321			point = NSMakePoint(i, j);
322            [self setColor:NSReadPixel(point) atPoint:point];
323        }
324        [pool release];
325    }
326	[compositedImage unlockFocus];
327}
328
329- (void)setPixelsByColor:newPixelsByColor
330{
331	[newPixelsByColor retain];
332	[pixelsByColor release];
333	pixelsByColor = newPixelsByColor;
334}
335
336- (void)setPixelsByPosition:newPixelsByPosition
337{
338	[newPixelsByPosition retain];
339	[pixelsByPosition release];
340	pixelsByPosition = newPixelsByPosition;
341}
342
343- copyWithZone:(NSZone *)zone
344{
345	id new = [[[self class] allocWithZone:zone] initWithSize:[self size]];
346	/*we're not using this approach because there seems to be a weirdness in -[NSMutableDictionary mutableCopy] that makes the mutably copied dictionary worthless-- it doesn't retain its objects!  At least, there were crashes when we used that method that didn't show up when we did -(void)setColor:atPoint: for each pixel.
347	[new setPixelsByColor:[[pixelsByColor mutableCopyWithZone:zone] autorelease]];
348	[new setPixelsByPosition:[[pixelsByPosition mutableCopyWithZone:zone] autorelease]];
349*/
350	unsigned int i, j;
351	for(i = 0; i < width; i++)
352	{
353		for(j = 0; j < height; j++)
354		{
355			[new setColor:[self colorAtPoint:NSMakePoint(i, j)] atPoint:NSMakePoint(i, j)];
356		}
357	}
358	return new;
359}
360
361@end
362
363
364@implementation PXImage(Archiving)
365
366NSString * PXCurrentVersion = @"r2v4";
367
368- legacyDiscoverPixelsByPositionFromPositionsByPixel:positionsByPixel
369{
370    id newPixelsByPosition = [NSMutableDictionary dictionaryWithCapacity:10000];
371    id pixelEnumerator = [positionsByPixel keyEnumerator];
372    id currentPixel;
373    while ( ( currentPixel = [pixelEnumerator nextObject] ) )
374    {
375        id positionEnumerator = [[positionsByPixel objectForKey:currentPixel] objectEnumerator];
376        id currentPosition;
377        while ( ( currentPosition = [positionEnumerator nextObject] ) )
378        {
379            [newPixelsByPosition setObject:currentPixel forKey:currentPosition];
380        }
381    }
382    return newPixelsByPosition;
383}
384
385- (NSSize)legacyDiscoverSizeFromPixelsByPosition:pixels
386{
387    float w = 0, h = 0;
388    id enumerator = [pixels keyEnumerator];
389    id current;
390    while ( ( current = [enumerator nextObject] ) )
391    {
392        NSPoint point = NSPointFromString(current);
393        if(point.x > w) { w = point.x; }
394        if(point.y > h) { h = point.y; }
395    }
396    return NSMakeSize(w+1, h+1);
397}
398
399- legacyDiscoverPixelsByPositionMatrixFromPixelsByPositionDictionary:pixels
400{
401    id realPixelsByPosition = [KTMutableMatrix matrixWithCapacity:512*512 cuboidBounds:(unsigned)(width), (unsigned)(height), 0, 0];
402	int i, j;
403	for(i = 0; i < width; i++)
404	{
405		for(j = 0; j < height; j++)
406		{
407			NSPoint point = NSMakePoint(i, j);
408			id pixel = [pixels objectForKey:NSStringFromPoint(point)];
409			//if(pixel == nil) { pixel = [self pixelOfColor:[NSColor clearColor]]; }
410			[realPixelsByPosition setObject:[pixel retain] atCoordinates:(unsigned)(point.x), (unsigned)(point.y)];
411		}
412	}
413    return realPixelsByPosition;
414}
415
416- legacyInitWithCoder:coder
417{
418    id positionsByPixel = [coder decodeObjectForKey:@"positionsByPixel"];
419    if(positionsByPixel != nil) { pixelsByPosition = [self legacyDiscoverPixelsByPositionFromPositionsByPixel:positionsByPixel]; }
420    if(NSEqualSizes([self size], NSZeroSize)) {
421		NSSize imageSize = [self legacyDiscoverSizeFromPixelsByPosition:pixelsByPosition];
422		width = imageSize.width;
423		height = imageSize.height;
424	}
425    pixelsByPosition = [[self legacyDiscoverPixelsByPositionMatrixFromPixelsByPositionDictionary:pixelsByPosition] retain];
426    return self;
427}
428
429- initWithCoder:coder
430{
431    [super init];
432    pixelsByColor = [[coder decodeObjectForKey:@"pixelsByColor"] mutableCopy];
433    pixelsByPosition = [[coder decodeObjectForKey:@"pixelsByPosition"] mutableCopy];
434    NSSize imageSize = [coder decodeSizeForKey:@"size"];
435	width = imageSize.width;
436	height = imageSize.height;
437    //if(![PXCurrentVersion isEqual:[coder decodeObjectForKey:@"version"]])
438	{
439		[self legacyInitWithCoder:coder];
440	}
441	rects = malloc(sizeof(NSRect) * (height + 2));
442	colors = malloc(sizeof(NSColor *) * (height + 2));
443    return self;
444}
445
446- (void)encodeWithCoder:coder
447{
448    //can't encode version until KTMatrix works properly.  we're still faking encoding in the old format.  sigh.
449    [coder encodeObject:PXCurrentVersion forKey:@"version"];
450    [coder encodeSize:[self size] forKey:@"size"];
451    [coder encodeObject:pixelsByColor forKey:@"pixelsByColor"];
452    //[coder encodeObject:pixelsByPosition forKey:@"pixelsByPosition"];
453    id fakePixelsByPosition = [NSMutableDictionary dictionaryWithCapacity:50000];
454    unsigned i, j;
455    for(i = 0; i < width; i++)
456    {
457        for(j = 0; j < height; j++)
458        {
459            id pixel = [pixelsByPosition objectAtCoordinates:i, j];
460            if(pixel != nil) { [fakePixelsByPosition setObject:pixel forKey:NSStringFromPoint(NSMakePoint(i, j))]; }
461        }
462    }
463    [coder encodeObject:fakePixelsByPosition forKey:@"pixelsByPosition"];
464}
465
466@end
467