| /* Copyright 2016 Pierre Ossman for Cendio AB |
| * |
| * This is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This software is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this software; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, |
| * USA. |
| */ |
| |
| #include <assert.h> |
| |
| #include <ApplicationServices/ApplicationServices.h> |
| |
| #include <FL/Fl_RGB_Image.H> |
| #include <FL/Fl_Window.H> |
| #include <FL/x.H> |
| |
| #include <rdr/Exception.h> |
| |
| #include "cocoa.h" |
| #include "Surface.h" |
| |
| static CGColorSpaceRef srgb = CGColorSpaceCreateWithName(kCGColorSpaceSRGB); |
| |
| static CGImageRef create_image(CGColorSpaceRef lut, |
| const unsigned char* data, |
| int w, int h, bool skip_alpha) |
| { |
| CGDataProviderRef provider; |
| CGImageAlphaInfo alpha; |
| |
| CGImageRef image; |
| |
| provider = CGDataProviderCreateWithData(NULL, data, |
| w * h * 4, NULL); |
| if (!provider) |
| throw rdr::Exception("CGDataProviderCreateWithData"); |
| |
| // FIXME: This causes a performance hit, but is necessary to avoid |
| // artifacts in the edges of the window |
| if (skip_alpha) |
| alpha = kCGImageAlphaNoneSkipFirst; |
| else |
| alpha = kCGImageAlphaPremultipliedFirst; |
| |
| image = CGImageCreate(w, h, 8, 32, w * 4, lut, |
| alpha | kCGBitmapByteOrder32Little, |
| provider, NULL, false, kCGRenderingIntentDefault); |
| CGDataProviderRelease(provider); |
| if (!image) |
| throw rdr::Exception("CGImageCreate"); |
| |
| return image; |
| } |
| |
| static void 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) |
| { |
| CGRect rect; |
| CGImageRef image, subimage; |
| |
| image = create_image(lut, data, src_w, src_h, mode == kCGBlendModeCopy); |
| |
| rect.origin.x = src_x; |
| rect.origin.y = src_y; |
| rect.size.width = w; |
| rect.size.height = h; |
| |
| subimage = CGImageCreateWithImageInRect(image, rect); |
| if (!subimage) |
| throw rdr::Exception("CGImageCreateImageWithImageInRect"); |
| |
| CGContextSaveGState(gc); |
| |
| CGContextSetBlendMode(gc, mode); |
| CGContextSetAlpha(gc, alpha); |
| |
| rect.origin.x = x; |
| rect.origin.y = y; |
| rect.size.width = w; |
| rect.size.height = h; |
| |
| CGContextDrawImage(gc, rect, subimage); |
| |
| CGContextRestoreGState(gc); |
| |
| CGImageRelease(subimage); |
| CGImageRelease(image); |
| } |
| |
| static CGContextRef make_bitmap(int width, int height, unsigned char* data) |
| { |
| CGContextRef bitmap; |
| |
| bitmap = CGBitmapContextCreate(data, width, height, 8, width*4, srgb, |
| kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little); |
| if (!bitmap) |
| throw rdr::Exception("CGBitmapContextCreate"); |
| |
| return bitmap; |
| } |
| |
| void Surface::clear(unsigned char r, unsigned char g, unsigned char b, unsigned char a) |
| { |
| unsigned char* out; |
| int x, y; |
| |
| r = (unsigned)r * a / 255; |
| g = (unsigned)g * a / 255; |
| b = (unsigned)b * a / 255; |
| |
| out = data; |
| for (y = 0;y < width();y++) { |
| for (x = 0;x < height();x++) { |
| *out++ = b; |
| *out++ = g; |
| *out++ = r; |
| *out++ = a; |
| } |
| } |
| } |
| |
| void Surface::draw(int src_x, int src_y, int x, int y, int w, int h) |
| { |
| CGColorSpaceRef lut; |
| |
| CGContextSaveGState(fl_gc); |
| |
| // Reset the transformation matrix back to the default identity |
| // matrix as otherwise we get a massive performance hit |
| CGContextConcatCTM(fl_gc, CGAffineTransformInvert(CGContextGetCTM(fl_gc))); |
| |
| // macOS Coordinates are from bottom left, not top left |
| y = Fl_Window::current()->h() - (y + h); |
| |
| lut = cocoa_win_color_space(Fl_Window::current()); |
| render(fl_gc, lut, data, kCGBlendModeCopy, 1.0, |
| src_x, src_y, width(), height(), x, y, w, h); |
| CGColorSpaceRelease(lut); |
| |
| CGContextRestoreGState(fl_gc); |
| } |
| |
| void Surface::draw(Surface* dst, int src_x, int src_y, int x, int y, int w, int h) |
| { |
| CGContextRef bitmap; |
| |
| bitmap = make_bitmap(dst->width(), dst->height(), dst->data); |
| |
| // macOS Coordinates are from bottom left, not top left |
| y = dst->height() - (y + h); |
| |
| render(bitmap, srgb, data, kCGBlendModeCopy, 1.0, |
| src_x, src_y, width(), height(), x, y, w, h); |
| |
| CGContextRelease(bitmap); |
| } |
| |
| void Surface::blend(int src_x, int src_y, int x, int y, int w, int h, int a) |
| { |
| CGColorSpaceRef lut; |
| |
| CGContextSaveGState(fl_gc); |
| |
| // Reset the transformation matrix back to the default identity |
| // matrix as otherwise we get a massive performance hit |
| CGContextConcatCTM(fl_gc, CGAffineTransformInvert(CGContextGetCTM(fl_gc))); |
| |
| // macOS Coordinates are from bottom left, not top left |
| y = Fl_Window::current()->h() - (y + h); |
| |
| lut = cocoa_win_color_space(Fl_Window::current()); |
| render(fl_gc, lut, data, kCGBlendModeNormal, (CGFloat)a/255.0, |
| src_x, src_y, width(), height(), x, y, w, h); |
| CGColorSpaceRelease(lut); |
| |
| CGContextRestoreGState(fl_gc); |
| } |
| |
| void Surface::blend(Surface* dst, int src_x, int src_y, int x, int y, int w, int h, int a) |
| { |
| CGContextRef bitmap; |
| |
| bitmap = make_bitmap(dst->width(), dst->height(), dst->data); |
| |
| // macOS Coordinates are from bottom left, not top left |
| y = dst->height() - (y + h); |
| |
| render(bitmap, srgb, data, kCGBlendModeNormal, (CGFloat)a/255.0, |
| src_x, src_y, width(), height(), x, y, w, h); |
| |
| CGContextRelease(bitmap); |
| } |
| |
| void Surface::alloc() |
| { |
| data = new unsigned char[width() * height() * 4]; |
| } |
| |
| void Surface::dealloc() |
| { |
| delete [] data; |
| } |
| |
| void Surface::update(const Fl_RGB_Image* image) |
| { |
| int x, y; |
| const unsigned char* in; |
| unsigned char* out; |
| |
| assert(image->w() == width()); |
| assert(image->h() == height()); |
| |
| // Convert data and pre-multiply alpha |
| in = (const unsigned char*)image->data()[0]; |
| out = data; |
| for (y = 0;y < image->h();y++) { |
| for (x = 0;x < image->w();x++) { |
| switch (image->d()) { |
| case 1: |
| *out++ = in[0]; |
| *out++ = in[0]; |
| *out++ = in[0]; |
| *out++ = 0xff; |
| break; |
| case 2: |
| *out++ = (unsigned)in[0] * in[1] / 255; |
| *out++ = (unsigned)in[0] * in[1] / 255; |
| *out++ = (unsigned)in[0] * in[1] / 255; |
| *out++ = in[1]; |
| break; |
| case 3: |
| *out++ = in[2]; |
| *out++ = in[1]; |
| *out++ = in[0]; |
| *out++ = 0xff; |
| break; |
| case 4: |
| *out++ = (unsigned)in[2] * in[3] / 255; |
| *out++ = (unsigned)in[1] * in[3] / 255; |
| *out++ = (unsigned)in[0] * in[3] / 255; |
| *out++ = in[3]; |
| break; |
| } |
| in += image->d(); |
| } |
| if (image->ld() != 0) |
| in += image->ld() - image->w() * image->d(); |
| } |
| } |