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