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