1/* Image support for the NeXT/Open/GNUstep and macOS window system. 2 Copyright (C) 1989, 1992-1994, 2005-2006, 2008-2021 Free Software 3 Foundation, Inc. 4 5This file is part of GNU Emacs. 6 7GNU Emacs is free software: you can redistribute it and/or modify 8it under the terms of the GNU General Public License as published by 9the Free Software Foundation, either version 3 of the License, or (at 10your option) any later version. 11 12GNU Emacs is distributed in the hope that it will be useful, 13but WITHOUT ANY WARRANTY; without even the implied warranty of 14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15GNU General Public License for more details. 16 17You should have received a copy of the GNU General Public License 18along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ 19 20/* 21Originally by Carl Edman 22Updated by Christian Limpach (chris@nice.ch) 23OpenStep/Rhapsody port by Scott Bender (sbender@harmony-ds.com) 24macOS/Aqua port by Christophe de Dinechin (descubes@earthlink.net) 25GNUstep port and post-20 update by Adrian Robert (arobert@cogsci.ucsd.edu) 26*/ 27 28/* This should be the first include, as it may set up #defines affecting 29 interpretation of even the system includes. */ 30#include <config.h> 31 32#include "lisp.h" 33#include "dispextern.h" 34#include "nsterm.h" 35#include "frame.h" 36#include "coding.h" 37 38 39#if defined (NS_IMPL_GNUSTEP) || MAC_OS_X_VERSION_MAX_ALLOWED < 1070 40# define COLORSPACE_NAME NSCalibratedRGBColorSpace 41#else 42# define COLORSPACE_NAME \ 43 ((ns_use_srgb_colorspace && NSAppKitVersionNumber >= NSAppKitVersionNumber10_7) \ 44 ? NSDeviceRGBColorSpace : NSCalibratedRGBColorSpace) 45#endif 46 47 48/* ========================================================================== 49 50 C interface. This allows easy calling from C files. We could just 51 compile everything as Objective-C, but that might mean slower 52 compilation and possible difficulties on some platforms. 53 54 ========================================================================== */ 55 56bool 57ns_can_use_native_image_api (Lisp_Object type) 58{ 59 NSString *imageType = @"unknown"; 60 NSArray *types; 61 62 NSTRACE ("ns_can_use_native_image_api"); 63 64 if (EQ (type, Qnative_image)) 65 return YES; 66 67#ifdef NS_IMPL_COCOA 68 /* Work out the UTI of the image type. */ 69 if (EQ (type, Qjpeg)) 70 imageType = @"public.jpeg"; 71 else if (EQ (type, Qpng)) 72 imageType = @"public.png"; 73 else if (EQ (type, Qgif)) 74 imageType = @"com.compuserve.gif"; 75 else if (EQ (type, Qtiff)) 76 imageType = @"public.tiff"; 77 else if (EQ (type, Qsvg)) 78 imageType = @"public.svg-image"; 79 else if (EQ (type, Qheic)) 80 imageType = @"public.heic"; 81 82 /* NSImage also supports a host of other types such as PDF and BMP, 83 but we don't yet support these in image.c. */ 84 85 types = [NSImage imageTypes]; 86#else 87 /* Work out the image type. */ 88 if (EQ (type, Qjpeg)) 89 imageType = @"jpeg"; 90 else if (EQ (type, Qpng)) 91 imageType = @"png"; 92 else if (EQ (type, Qgif)) 93 imageType = @"gif"; 94 else if (EQ (type, Qtiff)) 95 imageType = @"tiff"; 96 97 types = [NSImage imageFileTypes]; 98#endif 99 100 /* Check if the type is supported on this system. */ 101 if ([types indexOfObject:imageType] != NSNotFound) 102 return YES; 103 else 104 return NO; 105} 106 107void * 108ns_image_from_XBM (char *bits, int width, int height, 109 unsigned long fg, unsigned long bg) 110{ 111 NSTRACE ("ns_image_from_XBM"); 112 return [[EmacsImage alloc] initFromXBM: (unsigned char *) bits 113 width: width height: height 114 fg: fg bg: bg reverseBytes: YES]; 115} 116 117void * 118ns_image_for_XPM (int width, int height, int depth) 119{ 120 NSTRACE ("ns_image_for_XPM"); 121 return [[EmacsImage alloc] initForXPMWithDepth: depth 122 width: width height: height]; 123} 124 125void * 126ns_image_from_file (Lisp_Object file) 127{ 128 NSTRACE ("ns_image_from_file"); 129 return [EmacsImage allocInitFromFile: file]; 130} 131 132bool 133ns_load_image (struct frame *f, struct image *img, 134 Lisp_Object spec_file, Lisp_Object spec_data) 135{ 136 EmacsImage *eImg = nil; 137 NSSize size; 138 Lisp_Object lisp_index; 139 unsigned int index; 140 141 NSTRACE ("ns_load_image"); 142 143 eassert (valid_image_p (img->spec)); 144 145 lisp_index = Fplist_get (XCDR (img->spec), QCindex); 146 index = FIXNUMP (lisp_index) ? XFIXNAT (lisp_index) : 0; 147 148 if (STRINGP (spec_file)) 149 { 150 eImg = [EmacsImage allocInitFromFile: spec_file]; 151 } 152 else if (STRINGP (spec_data)) 153 { 154 NSData *data; 155 156 data = [NSData dataWithBytes: SSDATA (spec_data) 157 length: SBYTES (spec_data)]; 158 eImg = [[EmacsImage alloc] initWithData: data]; 159 [eImg setPixmapData]; 160 } 161 162 if (eImg == nil) 163 { 164 add_to_log ("Unable to load image %s", img->spec); 165 return 0; 166 } 167 168 if (![eImg setFrame: index]) 169 { 170 add_to_log ("Unable to set index %d for image %s", 171 make_fixnum (index), img->spec); 172 return 0; 173 } 174 175 img->lisp_data = [eImg getMetadata]; 176 177 size = [eImg size]; 178 img->width = size.width; 179 img->height = size.height; 180 181 /* 4) set img->pixmap = emacsimage */ 182 img->pixmap = eImg; 183 184 return 1; 185} 186 187 188int 189ns_image_width (void *img) 190{ 191 return [(id)img size].width; 192} 193 194int 195ns_image_height (void *img) 196{ 197 return [(id)img size].height; 198} 199 200void 201ns_image_set_size (void *img, int width, int height) 202{ 203 [(EmacsImage *)img setSize:NSMakeSize (width, height)]; 204} 205 206void 207ns_image_set_transform (void *img, double m[3][3]) 208{ 209 [(EmacsImage *)img setTransform:m]; 210} 211 212void 213ns_image_set_smoothing (void *img, bool smooth) 214{ 215 [(EmacsImage *)img setSmoothing:smooth]; 216} 217 218unsigned long 219ns_get_pixel (void *img, int x, int y) 220{ 221 return [(EmacsImage *)img getPixelAtX: x Y: y]; 222} 223 224void 225ns_put_pixel (void *img, int x, int y, unsigned long argb) 226{ 227 unsigned char alpha = (argb >> 24) & 0xFF; 228 if (alpha == 0) 229 alpha = 0xFF; 230 [(EmacsImage *)img setPixelAtX: x Y: y toRed: (argb >> 16) & 0xFF 231 green: (argb >> 8) & 0xFF blue: (argb & 0xFF) alpha: alpha]; 232} 233 234void 235ns_set_alpha (void *img, int x, int y, unsigned char a) 236{ 237 [(EmacsImage *)img setAlphaAtX: x Y: y to: a]; 238} 239 240size_t 241ns_image_size_in_bytes (void *img) 242{ 243 return [(EmacsImage *)img sizeInBytes]; 244} 245 246/* ========================================================================== 247 248 Class supporting bitmaps and images of various sorts. 249 250 ========================================================================== */ 251 252@implementation EmacsImage 253 254+ (instancetype)allocInitFromFile: (Lisp_Object)file 255{ 256 NSImageRep *imgRep; 257 Lisp_Object found; 258 EmacsImage *image; 259 NSString *filename; 260 261 /* Search bitmap-file-path for the file, if appropriate. */ 262 found = image_find_image_file (file); 263 if (!STRINGP (found)) 264 return nil; 265 filename = [NSString stringWithLispString:found]; 266 267 image = [[EmacsImage alloc] initByReferencingFile:filename]; 268 269 image->bmRep = nil; 270 if (![image isValid]) 271 { 272 [image release]; 273 return nil; 274 } 275 imgRep = [[image representations] firstObject]; 276 277 [image setSize: NSMakeSize([imgRep pixelsWide], [imgRep pixelsHigh])]; 278 [image setName:filename]; 279 280 return image; 281} 282 283 284- (void)dealloc 285{ 286 [stippleMask release]; 287 [bmRep release]; 288 [transform release]; 289 [super dealloc]; 290} 291 292 293- (id)copyWithZone:(NSZone *)zone 294{ 295 EmacsImage *copy = [super copyWithZone:zone]; 296 297 copy->stippleMask = [stippleMask copyWithZone:zone]; 298 copy->bmRep = [bmRep copyWithZone:zone]; 299 copy->transform = [transform copyWithZone:zone]; 300 301 return copy; 302} 303 304 305/* Create image from monochrome bitmap. If both FG and BG are 0 306 (black), set the background to white and make it transparent. */ 307- (instancetype)initFromXBM: (unsigned char *)bits width: (int)w height: (int)h 308 fg: (unsigned long)fg bg: (unsigned long)bg 309 reverseBytes: (BOOL)reverse 310{ 311 unsigned char *planes[5]; 312 unsigned char bg_alpha = 0xff; 313 314 [self initWithSize: NSMakeSize (w, h)]; 315 316 bmRep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes: NULL 317 pixelsWide: w pixelsHigh: h 318 bitsPerSample: 8 samplesPerPixel: 4 319 hasAlpha: YES isPlanar: YES 320 colorSpaceName: COLORSPACE_NAME 321 bytesPerRow: w bitsPerPixel: 0]; 322 323 [bmRep getBitmapDataPlanes: planes]; 324 325 if (fg == 0 && bg == 0) 326 { 327 bg = 0xffffff; 328 bg_alpha = 0; 329 } 330 331 { 332 /* Pull bits out to set the (bytewise) alpha mask. */ 333 unsigned char swt[16] = {0, 8, 4, 12, 2, 10, 6, 14, 334 1, 9, 5, 13, 3, 11, 7, 15}; 335 int i, j, k; 336 unsigned char *s = bits; 337 unsigned char *rr = planes[0]; 338 unsigned char *gg = planes[1]; 339 unsigned char *bb = planes[2]; 340 unsigned char *alpha = planes[3]; 341 unsigned char fgr = (fg >> 16) & 0xff; 342 unsigned char fgg = (fg >> 8) & 0xff; 343 unsigned char fgb = fg & 0xff; 344 unsigned char bgr = (bg >> 16) & 0xff; 345 unsigned char bgg = (bg >> 8) & 0xff; 346 unsigned char bgb = bg & 0xff; 347 unsigned char c; 348 349 for (j = 0; j < h; ++j) 350 for (i = 0; i < w; ) 351 { 352 c = *s++; 353 354 /* XBM files have the bits in reverse order within each byte 355 as compared to our fringe bitmaps. This function deals 356 with both so has to be able to handle the bytes in either 357 order. */ 358 if (reverse) 359 c = swt[c >> 4] | (swt[c & 0xf] << 4); 360 361 for (k = 0; i < w && k < 8; ++k, ++i) 362 { 363 if (c & 0x80) 364 { 365 *rr++ = fgr; 366 *gg++ = fgg; 367 *bb++ = fgb; 368 *alpha++ = 0xff; 369 } 370 else 371 { 372 *rr++ = bgr; 373 *gg++ = bgg; 374 *bb++ = bgb; 375 *alpha++ = bg_alpha; 376 } 377 c <<= 1; 378 } 379 } 380 } 381 382 [self addRepresentation: bmRep]; 383 return self; 384} 385 386 387- (instancetype)initForXPMWithDepth: (int)depth width: (int)width height: (int)height 388{ 389 NSSize s = {width, height}; 390 int i; 391 392 [self initWithSize: s]; 393 394 bmRep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes: NULL 395 pixelsWide: width pixelsHigh: height 396 /* keep things simple for now */ 397 bitsPerSample: 8 samplesPerPixel: 4 /*RGB+A*/ 398 hasAlpha: YES isPlanar: YES 399 colorSpaceName: COLORSPACE_NAME 400 bytesPerRow: width bitsPerPixel: 0]; 401 402 [bmRep getBitmapDataPlanes: pixmapData]; 403 for (i =0; i<4; i++) 404 memset (pixmapData[i], 0, width*height); 405 [self addRepresentation: bmRep]; 406 return self; 407} 408 409 410/* Attempt to pull out pixmap data from a BitmapImageRep; returns NO if fails. */ 411- (void) setPixmapData 412{ 413 NSEnumerator *reps; 414 NSImageRep *rep; 415 416 reps = [[self representations] objectEnumerator]; 417 while ((rep = (NSImageRep *) [reps nextObject])) 418 { 419 if ([rep respondsToSelector: @selector (getBitmapDataPlanes:)]) 420 { 421 NSBitmapImageRep *bmr = (NSBitmapImageRep *) rep; 422 423 if ([bmr numberOfPlanes] >= 3) 424 [bmr getBitmapDataPlanes: pixmapData]; 425 426 [self setSize: NSMakeSize([bmr pixelsWide], [bmr pixelsHigh])]; 427 428 break; 429 } 430 } 431} 432 433 434/* Note: this and next work only for image created with initForXPMWithDepth, 435 initFromSkipXBM, or where setPixmapData was called successfully. */ 436/* return ARGB */ 437- (unsigned long) getPixelAtX: (int)x Y: (int)y 438{ 439 if (bmRep == nil) 440 return 0; 441 442 /* This method is faster but won't work for bitmaps. */ 443 if (pixmapData[0] != NULL) 444 { 445 int loc = x + y * [self size].width; 446 return (((unsigned long) pixmapData[3][loc] << 24) /* alpha */ 447 | ((unsigned long) pixmapData[0][loc] << 16) 448 | ((unsigned long) pixmapData[1][loc] << 8) 449 | (unsigned long) pixmapData[2][loc]); 450 } 451 else 452 { 453 NSColor *color = [bmRep colorAtX: x y: y]; 454 EmacsCGFloat r, g, b, a; 455 [color getRed: &r green: &g blue: &b alpha: &a]; 456 return ((int)(a * 255.0) << 24) 457 | ((int)(r * 255.0) << 16) | ((int)(g * 255.0) << 8) 458 | ((int)(b * 255.0)); 459 460 } 461} 462 463- (void) setPixelAtX: (int)x Y: (int)y toRed: (unsigned char)r 464 green: (unsigned char)g blue: (unsigned char)b 465 alpha:(unsigned char)a 466{ 467 if (bmRep == nil) 468 return; 469 470 if (pixmapData[0] != NULL) 471 { 472 int loc = x + y * [self size].width; 473 pixmapData[0][loc] = r; 474 pixmapData[1][loc] = g; 475 pixmapData[2][loc] = b; 476 pixmapData[3][loc] = a; 477 } 478 else 479 { 480 [bmRep setColor: 481 [NSColor colorWithCalibratedRed: (r/255.0) green: (g/255.0) 482 blue: (b/255.0) alpha: (a/255.0)] 483 atX: x y: y]; 484 } 485} 486 487- (void) setAlphaAtX: (int) x Y: (int) y to: (unsigned char) a 488{ 489 if (bmRep == nil) 490 return; 491 492 if (pixmapData[0] != NULL) 493 { 494 int loc = x + y * [self size].width; 495 496 pixmapData[3][loc] = a; 497 } 498 else 499 { 500 NSColor *color = [bmRep colorAtX: x y: y]; 501 color = [color colorWithAlphaComponent: (a / 255.0)]; 502 [bmRep setColor: color atX: x y: y]; 503 } 504} 505 506/* Returns a pattern color, which is cached here. */ 507- (NSColor *)stippleMask 508{ 509 if (stippleMask == nil) 510 stippleMask = [[NSColor colorWithPatternImage: self] retain]; 511 return stippleMask; 512} 513 514/* Find the first NSBitmapImageRep which has multiple frames. */ 515- (NSBitmapImageRep *)getAnimatedBitmapImageRep 516{ 517 for (NSImageRep * r in [self representations]) 518 { 519 if ([r isKindOfClass:[NSBitmapImageRep class]]) 520 { 521 NSBitmapImageRep * bm = (NSBitmapImageRep *)r; 522 if ([[bm valueForProperty:NSImageFrameCount] intValue] > 0) 523 return bm; 524 } 525 } 526 return nil; 527} 528 529/* If the image has multiple frames, get a count of them and the 530 animation delay, if available. */ 531- (Lisp_Object)getMetadata 532{ 533 Lisp_Object metadata = Qnil; 534 535 NSBitmapImageRep * bm = [self getAnimatedBitmapImageRep]; 536 537 if (bm != nil) 538 { 539 int frames = [[bm valueForProperty:NSImageFrameCount] intValue]; 540 float delay = [[bm valueForProperty:NSImageCurrentFrameDuration] 541 floatValue]; 542 543 if (frames > 1) 544 metadata = Fcons (Qcount, Fcons (make_fixnum (frames), metadata)); 545 if (delay > 0) 546 metadata = Fcons (Qdelay, Fcons (make_float (delay), metadata)); 547 } 548 return metadata; 549} 550 551/* Attempt to set the animation frame to be displayed. */ 552- (BOOL)setFrame: (unsigned int) index 553{ 554 NSBitmapImageRep * bm = [self getAnimatedBitmapImageRep]; 555 556 if (bm != nil) 557 { 558 int frames = [[bm valueForProperty:NSImageFrameCount] intValue]; 559 560 /* If index is invalid, give up. */ 561 if (index < 0 || index > frames) 562 return NO; 563 564 [bm setProperty: NSImageCurrentFrame 565 withValue: [NSNumber numberWithUnsignedInt:index]]; 566 } 567 568 /* Setting the frame has succeeded, or the image doesn't have 569 multiple frames. */ 570 return YES; 571} 572 573- (void)setTransform: (double[3][3]) m 574{ 575 transform = [[NSAffineTransform transform] retain]; 576 NSAffineTransformStruct tm 577 = { m[0][0], m[0][1], m[1][0], m[1][1], m[2][0], m[2][1]}; 578 [transform setTransformStruct:tm]; 579} 580 581- (void)setSmoothing: (BOOL) s 582{ 583 smoothing = s; 584} 585 586/* Approximate allocated size of image in bytes. */ 587- (size_t) sizeInBytes 588{ 589 size_t bytes = 0; 590 NSImageRep *rep; 591 NSEnumerator *reps = [[self representations] objectEnumerator]; 592 while ((rep = (NSImageRep *) [reps nextObject])) 593 { 594 if ([rep respondsToSelector: @selector (bytesPerRow)]) 595 { 596 NSBitmapImageRep *bmr = (NSBitmapImageRep *) rep; 597 bytes += [bmr bytesPerRow] * [bmr numberOfPlanes] * [bmr pixelsHigh]; 598 } 599 } 600 return bytes; 601} 602 603 604@end 605