1/*
2    PPGNUstepGlue_BitmapNonpremultipliedPNG.m
3
4    Copyright 2014-2018 Josh Freeman
5    http://www.twilightedge.com
6
7    This file is part of PikoPixel for GNUstep.
8    PikoPixel is a graphical application for drawing & editing pixel-art images.
9
10    PikoPixel is free software: you can redistribute it and/or modify it under
11    the terms of the GNU Affero General Public License as published by the
12    Free Software Foundation, either version 3 of the License, or (at your
13    option) any later version approved for PikoPixel by its copyright holder (or
14    an authorized proxy).
15
16    PikoPixel is distributed in the hope that it will be useful, but WITHOUT ANY
17    WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18    FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
19    details.
20
21    You should have received a copy of the GNU Affero General Public License
22    along with this program. If not, see <http://www.gnu.org/licenses/>.
23*/
24
25#ifdef GNUSTEP
26
27#import <Cocoa/Cocoa.h>
28#import "NSObject_PPUtilities.h"
29#import "PPAppBootUtilities.h"
30#import "PPApplication.h"
31#import "PPImagePixelAlphaPremultiplyTables.h"
32#import "NSBitmapImageRep_PPUtilities.h"
33
34
35static bool gAllowInPlaceUnpremultiply = NO, gBitmapSourceIsExternal = NO;
36
37
38@interface NSBitmapImageRep (PPGNUstepGlue_BitmapNonpremultipliedPNGUtilities)
39
40- (bool) ppGSGlue_CanPremultiplyImportedBitmap;
41
42- (bool) ppGSGlue_Premultiply;
43- (bool) ppGSGlue_Unpremultiply;
44
45- (NSBitmapImageRep *) ppGSGlue_UnpremultipliedBitmap;
46
47@end
48
49@interface NSImage (PPGNUstepGlue_BitmapNonpremultipliedPNGUtilities)
50
51- (void) ppGSGlue_PremultiplyBitmapRepresentations;
52
53@end
54
55@implementation NSObject (PPGNUstepGlue_BitmapNonpremultipliedPNG)
56
57+ (void) ppGSGlue_AA_BitmapNonpremultipliedPNG_InstallPatches
58{
59    macroSwizzleInstanceMethod(NSBitmapImageRep, _initBitmapFromPNG:,
60                                ppGSPatch_InitBitmapFromPNG:);
61
62    macroSwizzleInstanceMethod(NSBitmapImageRep, _PNGRepresentationWithProperties:,
63                                ppGSPatch_PNGRepresentationWithProperties:);
64
65    macroSwizzleInstanceMethod(NSBitmapImageRep, ppCompressedTIFFDataFromBounds:,
66                                ppGSPatch_CompressedTIFFDataFromBounds:);
67
68    macroSwizzleClassMethod(NSBitmapImageRep, ppImageBitmapWithImportedData:,
69                                ppGSPatch_ImageBitmapWithImportedData:);
70}
71
72+ (void) load
73{
74    // AA_ was inserted into the name of the _InstallPatches method to make sure it gets called
75    // before ppGSGlue_BitmapGraphicsContext_Install (AfterAppLoads selectors are called
76    // alphabetically) - this is because installing the BitmapGraphicsContext patches causes
77    // +[PPCanvasView initialize] to be called, which loads some PNG images (for selection-tool
78    // overlay pattern colors)
79
80    macroPerformNSObjectSelectorAfterAppLoads(
81                                        ppGSGlue_AA_BitmapNonpremultipliedPNG_InstallPatches);
82}
83
84@end
85
86@implementation PPApplication (PPGNUstepGlue_BitmapNonpremultipliedPNG)
87
88// OVERRIDE: -[NSApplication setApplicationIconImage:]
89//  GNUstep sets up the application icon before the app finishes launching (& before PikoPixel
90// installs its patches), so -[NSApplication setApplicationIconImage:] is "patched" by
91// implementing a subclass override method in PPApplication (PPApplication doesn't implement
92// this method elsewhere).
93//  The override manually premultiplies the bitmap representations in the icon image, since
94// the -[NSBitmapImageRep initBitmapFromPNG:] patch that would automatically premultiply them
95// as they're initialized isn't installed yet.
96
97- (void) setApplicationIconImage: (NSImage *) anImage
98{
99    [anImage ppGSGlue_PremultiplyBitmapRepresentations];
100
101    [super setApplicationIconImage: anImage];
102}
103
104@end
105
106@implementation NSBitmapImageRep (PPGNUstepGlue_BitmapNonpremultipliedPNG)
107
108- (id) ppGSPatch_InitBitmapFromPNG: (NSData *) imageData
109{
110    self = [self ppGSPatch_InitBitmapFromPNG: imageData];
111
112    [self ppGSGlue_Premultiply];
113
114    return self;
115}
116
117- (NSData *) ppGSPatch_PNGRepresentationWithProperties: (NSDictionary *) properties
118{
119    if (gAllowInPlaceUnpremultiply)
120    {
121        [self ppGSGlue_Unpremultiply];
122    }
123    else
124    {
125        self = [self ppGSGlue_UnpremultipliedBitmap];
126    }
127
128    return [self ppGSPatch_PNGRepresentationWithProperties: properties];
129}
130
131// PATCH: -[NSBitmapImageRep (PPUtilities) ppCompressedTIFFDataFromBounds:]
132// ppCompressedTIFFData (called by ppCompressedTIFFDataFromBounds:) is currently patched on
133// GNUstep to return PNG data (OS X had issues reading GNUstep-written TIFF data), and it uses
134// a temporary, single-use bitmap for constructing the PNG data, so unpremultiplying in-place
135// is quicker than allocating & converting an additional temporary bitmap.
136
137- (NSData *) ppGSPatch_CompressedTIFFDataFromBounds: (NSRect) bounds
138{
139    NSData *returnedData;
140
141    gAllowInPlaceUnpremultiply = YES;
142
143    returnedData = [self ppGSPatch_CompressedTIFFDataFromBounds: bounds];
144
145    gAllowInPlaceUnpremultiply = NO;
146
147    return returnedData;
148}
149
150+ (NSBitmapImageRep *) ppGSPatch_ImageBitmapWithImportedData: (NSData *) importedData
151{
152    NSBitmapImageRep *returnedBitmap;
153
154    gBitmapSourceIsExternal = YES;
155
156    returnedBitmap = [self ppGSPatch_ImageBitmapWithImportedData: importedData];
157
158    gBitmapSourceIsExternal = NO;
159
160    return returnedBitmap;
161}
162
163@end
164
165@implementation NSBitmapImageRep (PPGNUstepGlue_BitmapNonpremultipliedPNGUtilities)
166
167- (bool) ppGSGlue_CanPremultiplyImportedBitmap
168{
169    // Premultiply implementation only supports 4-channel bitmaps that have an alpha channel
170    // and 8 bits-per-sample; For locally-created bitmaps, ppIsImageBitmap is enough for
171    // verification, however, it only checks the number of channels, so external-source bitmaps
172    // also need explicit alpha & bitsPerSample checks before allowing premultiply
173
174    return (([self samplesPerPixel] == 4)
175                && [self hasAlpha]
176                && ([self bitsPerSample] == 8)) ? YES : NO;
177}
178
179- (bool) ppGSGlue_Premultiply
180{
181    NSSize bitmapSize;
182    unsigned char *bitmapRow, *premultiplyTable;
183    int bytesPerRow, pixelsPerRow, rowCounter, pixelCounter;
184    PPImageBitmapPixel *bitmapPixel;
185
186    if (!(_format & NSAlphaNonpremultipliedBitmapFormat)
187        || ![self ppIsImageBitmap]
188        || (gBitmapSourceIsExternal && ![self ppGSGlue_CanPremultiplyImportedBitmap]))
189    {
190        return NO;
191    }
192
193    bitmapSize = [self ppSizeInPixels];
194
195    bitmapRow = [self bitmapData];
196
197    if (!bitmapRow)
198        goto ERROR;
199
200    bytesPerRow = [self bytesPerRow];
201    pixelsPerRow = bitmapSize.width;
202
203    rowCounter = bitmapSize.height;
204
205    while (rowCounter--)
206    {
207        bitmapPixel = (PPImageBitmapPixel *) bitmapRow;
208
209        pixelCounter = pixelsPerRow;
210
211        while (pixelCounter--)
212        {
213            if (macroImagePixelComponent_Alpha(bitmapPixel) == 255)
214            {
215                bitmapPixel++;
216            }
217            else if (macroImagePixelComponent_Alpha(bitmapPixel) == 0)
218            {
219                *bitmapPixel++ = 0;
220            }
221            else
222            {
223                premultiplyTable = macroAlphaPremultiplyTableForImagePixel(bitmapPixel);
224
225                macroImagePixelComponent_Red(bitmapPixel) =
226                            premultiplyTable[macroImagePixelComponent_Red(bitmapPixel)];
227
228                macroImagePixelComponent_Green(bitmapPixel) =
229                            premultiplyTable[macroImagePixelComponent_Green(bitmapPixel)];
230
231                macroImagePixelComponent_Blue(bitmapPixel) =
232                            premultiplyTable[macroImagePixelComponent_Blue(bitmapPixel)];
233
234                bitmapPixel++;
235            }
236        }
237
238        bitmapRow += bytesPerRow;
239    }
240
241    _format &= ~NSAlphaNonpremultipliedBitmapFormat;
242
243    return YES;
244
245ERROR:
246    return NO;
247}
248
249- (bool) ppGSGlue_Unpremultiply
250{
251    NSSize bitmapSize;
252    unsigned char *bitmapRow, *unpremultiplyTable;
253    int bytesPerRow, pixelsPerRow, rowCounter, pixelCounter;
254    PPImageBitmapPixel *bitmapPixel;
255
256    if ((_format & NSAlphaNonpremultipliedBitmapFormat)
257        || ![self ppIsImageBitmap])
258    {
259        return NO;
260    }
261
262    bitmapSize = [self ppSizeInPixels];
263
264    bitmapRow = [self bitmapData];
265
266    if (!bitmapRow)
267        goto ERROR;
268
269    bytesPerRow = [self bytesPerRow];
270    pixelsPerRow = bitmapSize.width;
271
272    rowCounter = bitmapSize.height;
273
274    while (rowCounter--)
275    {
276        bitmapPixel = (PPImageBitmapPixel *) bitmapRow;
277
278        pixelCounter = pixelsPerRow;
279
280        while (pixelCounter--)
281        {
282            if ((macroImagePixelComponent_Alpha(bitmapPixel) == 255)
283                || (macroImagePixelComponent_Alpha(bitmapPixel) == 0))
284            {
285                bitmapPixel++;
286            }
287            else
288            {
289                unpremultiplyTable = macroAlphaUnpremultiplyTableForImagePixel(bitmapPixel);
290
291                macroImagePixelComponent_Red(bitmapPixel) =
292                            unpremultiplyTable[macroImagePixelComponent_Red(bitmapPixel)];
293
294                macroImagePixelComponent_Green(bitmapPixel) =
295                            unpremultiplyTable[macroImagePixelComponent_Green(bitmapPixel)];
296
297                macroImagePixelComponent_Blue(bitmapPixel) =
298                            unpremultiplyTable[macroImagePixelComponent_Blue(bitmapPixel)];
299
300                bitmapPixel++;
301            }
302        }
303
304        bitmapRow += bytesPerRow;
305    }
306
307    _format |= NSAlphaNonpremultipliedBitmapFormat;
308
309    return YES;
310
311ERROR:
312    return NO;
313}
314
315- (NSBitmapImageRep *) ppGSGlue_UnpremultipliedBitmap
316{
317    NSSize bitmapSize;
318    NSBitmapImageRep *destinationBitmap;
319    unsigned char *destinationRow, *sourceRow, *unpremultiplyTable;
320    int destinationBytesPerRow, sourceBytesPerRow, pixelsPerRow, rowCounter, pixelCounter;
321    PPImageBitmapPixel *destinationPixel, *sourcePixel;
322
323    if (([self bitmapFormat] & NSAlphaNonpremultipliedBitmapFormat)
324        || ![self ppIsImageBitmap])
325    {
326        return self;
327    }
328
329    bitmapSize = [self ppSizeInPixels];
330
331    destinationBitmap =
332                [[[NSBitmapImageRep alloc] initWithBitmapDataPlanes: NULL
333                                            pixelsWide: bitmapSize.width
334                                            pixelsHigh: bitmapSize.height
335                                            bitsPerSample: 8
336                                            samplesPerPixel: sizeof(PPImageBitmapPixel)
337                                            hasAlpha: YES
338                                            isPlanar: NO
339                                            colorSpaceName: NSCalibratedRGBColorSpace
340                                            bitmapFormat: NSAlphaNonpremultipliedBitmapFormat
341                                            bytesPerRow: 0
342                                            bitsPerPixel: 0]
343                                    autorelease];
344
345    if (!destinationBitmap)
346        goto ERROR;
347
348    destinationRow = [destinationBitmap bitmapData];
349    sourceRow = [self bitmapData];
350
351    if (!destinationRow || !sourceRow)
352    {
353        goto ERROR;
354    }
355
356    destinationBytesPerRow = [destinationBitmap bytesPerRow];
357    sourceBytesPerRow = [self bytesPerRow];
358
359    pixelsPerRow = bitmapSize.width;
360
361    rowCounter = bitmapSize.height;
362
363    while (rowCounter--)
364    {
365        destinationPixel = (PPImageBitmapPixel *) destinationRow;
366        sourcePixel = (PPImageBitmapPixel *) sourceRow;
367
368        pixelCounter = pixelsPerRow;
369
370        while (pixelCounter--)
371        {
372            if ((macroImagePixelComponent_Alpha(sourcePixel) == 255)
373                || (macroImagePixelComponent_Alpha(sourcePixel) == 0))
374            {
375                *destinationPixel++ = *sourcePixel++;
376            }
377            else
378            {
379                unpremultiplyTable = macroAlphaUnpremultiplyTableForImagePixel(sourcePixel);
380
381                macroImagePixelComponent_Red(destinationPixel) =
382                            unpremultiplyTable[macroImagePixelComponent_Red(sourcePixel)];
383
384                macroImagePixelComponent_Green(destinationPixel) =
385                            unpremultiplyTable[macroImagePixelComponent_Green(sourcePixel)];
386
387                macroImagePixelComponent_Blue(destinationPixel) =
388                            unpremultiplyTable[macroImagePixelComponent_Blue(sourcePixel)];
389
390                macroImagePixelComponent_Alpha(destinationPixel) =
391                            macroImagePixelComponent_Alpha(sourcePixel);
392
393                destinationPixel++;
394                sourcePixel++;
395            }
396        }
397
398        destinationRow += destinationBytesPerRow;
399        sourceRow += sourceBytesPerRow;
400    }
401
402    return destinationBitmap;
403
404ERROR:
405    return self;
406}
407
408@end
409
410@implementation NSImage (PPGNUstepGlue_BitmapNonpremultipliedPNGUtilities)
411
412- (void) ppGSGlue_PremultiplyBitmapRepresentations
413{
414    NSEnumerator *repEnumerator;
415    NSBitmapImageRep *bitmapRep;
416
417    repEnumerator = [[self representations] objectEnumerator];
418
419    while (bitmapRep = [repEnumerator nextObject])
420    {
421        if ([bitmapRep isKindOfClass: [NSBitmapImageRep class]]
422                && ([bitmapRep bitmapFormat] & NSAlphaNonpremultipliedBitmapFormat))
423        {
424            [bitmapRep ppGSGlue_Premultiply];
425        }
426    }
427}
428
429@end
430
431#endif  // GNUSTEP
432
433