1 /* Copyright 2016 Pierre Ossman for Cendio AB
2  *
3  * This is free software; you can redistribute it and/or modify
4  * it under the terms of the GNU General Public License as published by
5  * the Free Software Foundation; either version 2 of the License, or
6  * (at your option) any later version.
7  *
8  * This software is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this software; if not, write to the Free Software
15  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
16  * USA.
17  */
18 
19 #include <assert.h>
20 
21 #include <ApplicationServices/ApplicationServices.h>
22 
23 #include <FL/Fl_RGB_Image.H>
24 #include <FL/Fl_Window.H>
25 #include <FL/x.H>
26 
27 #include <rdr/Exception.h>
28 
29 #include "cocoa.h"
30 #include "Surface.h"
31 
32 static CGColorSpaceRef srgb = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
33 
create_image(CGColorSpaceRef lut,const unsigned char * data,int w,int h,bool skip_alpha)34 static CGImageRef create_image(CGColorSpaceRef lut,
35                                const unsigned char* data,
36                                int w, int h, bool skip_alpha)
37 {
38   CGDataProviderRef provider;
39   CGImageAlphaInfo alpha;
40 
41   CGImageRef image;
42 
43   provider = CGDataProviderCreateWithData(NULL, data,
44                                           w * h * 4, NULL);
45   if (!provider)
46     throw rdr::Exception("CGDataProviderCreateWithData");
47 
48   // FIXME: This causes a performance hit, but is necessary to avoid
49   //        artifacts in the edges of the window
50   if (skip_alpha)
51     alpha = kCGImageAlphaNoneSkipFirst;
52   else
53     alpha = kCGImageAlphaPremultipliedFirst;
54 
55   image = CGImageCreate(w, h, 8, 32, w * 4, lut,
56                         alpha | kCGBitmapByteOrder32Little,
57                         provider, NULL, false, kCGRenderingIntentDefault);
58   CGDataProviderRelease(provider);
59   if (!image)
60     throw rdr::Exception("CGImageCreate");
61 
62   return image;
63 }
64 
render(CGContextRef gc,CGColorSpaceRef lut,const unsigned char * data,CGBlendMode mode,CGFloat alpha,int src_x,int src_y,int src_w,int src_h,int x,int y,int w,int h)65 static void render(CGContextRef gc, CGColorSpaceRef lut,
66                    const unsigned char* data,
67                    CGBlendMode mode, CGFloat alpha,
68                    int src_x, int src_y, int src_w, int src_h,
69                    int x, int y, int w, int h)
70 {
71   CGRect rect;
72   CGImageRef image, subimage;
73 
74   image = create_image(lut, data, src_w, src_h, mode == kCGBlendModeCopy);
75 
76   rect.origin.x = src_x;
77   rect.origin.y = src_y;
78   rect.size.width = w;
79   rect.size.height = h;
80 
81   subimage = CGImageCreateWithImageInRect(image, rect);
82   if (!subimage)
83     throw rdr::Exception("CGImageCreateImageWithImageInRect");
84 
85   CGContextSaveGState(gc);
86 
87   CGContextSetBlendMode(gc, mode);
88   CGContextSetAlpha(gc, alpha);
89 
90   rect.origin.x = x;
91   rect.origin.y = y;
92   rect.size.width = w;
93   rect.size.height = h;
94 
95   CGContextDrawImage(gc, rect, subimage);
96 
97   CGContextRestoreGState(gc);
98 
99   CGImageRelease(subimage);
100   CGImageRelease(image);
101 }
102 
make_bitmap(int width,int height,unsigned char * data)103 static CGContextRef make_bitmap(int width, int height, unsigned char* data)
104 {
105   CGContextRef bitmap;
106 
107   bitmap = CGBitmapContextCreate(data, width, height, 8, width*4, srgb,
108                                  kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little);
109   if (!bitmap)
110     throw rdr::Exception("CGBitmapContextCreate");
111 
112   return bitmap;
113 }
114 
clear(unsigned char r,unsigned char g,unsigned char b,unsigned char a)115 void Surface::clear(unsigned char r, unsigned char g, unsigned char b, unsigned char a)
116 {
117   unsigned char* out;
118   int x, y;
119 
120   r = (unsigned)r * a / 255;
121   g = (unsigned)g * a / 255;
122   b = (unsigned)b * a / 255;
123 
124   out = data;
125   for (y = 0;y < width();y++) {
126     for (x = 0;x < height();x++) {
127       *out++ = b;
128       *out++ = g;
129       *out++ = r;
130       *out++ = a;
131     }
132   }
133 }
134 
draw(int src_x,int src_y,int x,int y,int w,int h)135 void Surface::draw(int src_x, int src_y, int x, int y, int w, int h)
136 {
137   CGColorSpaceRef lut;
138 
139   CGContextSaveGState(fl_gc);
140 
141   // Reset the transformation matrix back to the default identity
142   // matrix as otherwise we get a massive performance hit
143   CGContextConcatCTM(fl_gc, CGAffineTransformInvert(CGContextGetCTM(fl_gc)));
144 
145   // macOS Coordinates are from bottom left, not top left
146   y = Fl_Window::current()->h() - (y + h);
147 
148   lut = cocoa_win_color_space(Fl_Window::current());
149   render(fl_gc, lut, data, kCGBlendModeCopy, 1.0,
150          src_x, src_y, width(), height(), x, y, w, h);
151   CGColorSpaceRelease(lut);
152 
153   CGContextRestoreGState(fl_gc);
154 }
155 
draw(Surface * dst,int src_x,int src_y,int x,int y,int w,int h)156 void Surface::draw(Surface* dst, int src_x, int src_y, int x, int y, int w, int h)
157 {
158   CGContextRef bitmap;
159 
160   bitmap = make_bitmap(dst->width(), dst->height(), dst->data);
161 
162   // macOS Coordinates are from bottom left, not top left
163   y = dst->height() - (y + h);
164 
165   render(bitmap, srgb, data, kCGBlendModeCopy, 1.0,
166          src_x, src_y, width(), height(), x, y, w, h);
167 
168   CGContextRelease(bitmap);
169 }
170 
blend(int src_x,int src_y,int x,int y,int w,int h,int a)171 void Surface::blend(int src_x, int src_y, int x, int y, int w, int h, int a)
172 {
173   CGColorSpaceRef lut;
174 
175   CGContextSaveGState(fl_gc);
176 
177   // Reset the transformation matrix back to the default identity
178   // matrix as otherwise we get a massive performance hit
179   CGContextConcatCTM(fl_gc, CGAffineTransformInvert(CGContextGetCTM(fl_gc)));
180 
181   // macOS Coordinates are from bottom left, not top left
182   y = Fl_Window::current()->h() - (y + h);
183 
184   lut = cocoa_win_color_space(Fl_Window::current());
185   render(fl_gc, lut, data, kCGBlendModeNormal, (CGFloat)a/255.0,
186          src_x, src_y, width(), height(), x, y, w, h);
187   CGColorSpaceRelease(lut);
188 
189   CGContextRestoreGState(fl_gc);
190 }
191 
blend(Surface * dst,int src_x,int src_y,int x,int y,int w,int h,int a)192 void Surface::blend(Surface* dst, int src_x, int src_y, int x, int y, int w, int h, int a)
193 {
194   CGContextRef bitmap;
195 
196   bitmap = make_bitmap(dst->width(), dst->height(), dst->data);
197 
198   // macOS Coordinates are from bottom left, not top left
199   y = dst->height() - (y + h);
200 
201   render(bitmap, srgb, data, kCGBlendModeNormal, (CGFloat)a/255.0,
202          src_x, src_y, width(), height(), x, y, w, h);
203 
204   CGContextRelease(bitmap);
205 }
206 
alloc()207 void Surface::alloc()
208 {
209   data = new unsigned char[width() * height() * 4];
210 }
211 
dealloc()212 void Surface::dealloc()
213 {
214   delete [] data;
215 }
216 
update(const Fl_RGB_Image * image)217 void Surface::update(const Fl_RGB_Image* image)
218 {
219   int x, y;
220   const unsigned char* in;
221   unsigned char* out;
222 
223   assert(image->w() == width());
224   assert(image->h() == height());
225 
226   // Convert data and pre-multiply alpha
227   in = (const unsigned char*)image->data()[0];
228   out = data;
229   for (y = 0;y < image->h();y++) {
230     for (x = 0;x < image->w();x++) {
231       switch (image->d()) {
232       case 1:
233         *out++ = in[0];
234         *out++ = in[0];
235         *out++ = in[0];
236         *out++ = 0xff;
237         break;
238       case 2:
239         *out++ = (unsigned)in[0] * in[1] / 255;
240         *out++ = (unsigned)in[0] * in[1] / 255;
241         *out++ = (unsigned)in[0] * in[1] / 255;
242         *out++ = in[1];
243         break;
244       case 3:
245         *out++ = in[2];
246         *out++ = in[1];
247         *out++ = in[0];
248         *out++ = 0xff;
249         break;
250       case 4:
251         *out++ = (unsigned)in[2] * in[3] / 255;
252         *out++ = (unsigned)in[1] * in[3] / 255;
253         *out++ = (unsigned)in[0] * in[3] / 255;
254         *out++ = in[3];
255         break;
256       }
257       in += image->d();
258     }
259     if (image->ld() != 0)
260       in += image->ld() - image->w() * image->d();
261   }
262 }
263