diff --git a/FL/Fl_Image_Surface.H b/FL/Fl_Image_Surface.H index af45eb303..fd0562107 100644 --- a/FL/Fl_Image_Surface.H +++ b/FL/Fl_Image_Surface.H @@ -21,6 +21,7 @@ #include #include +#include /** Directs all graphics requests to an Fl_Image. @@ -45,6 +46,7 @@ */ class FL_EXPORT Fl_Image_Surface : public Fl_Surface_Device { private: + void prepare_(int w, int h, int highres); Fl_Offscreen offscreen; int width; int height; @@ -63,12 +65,18 @@ private: public: static const char *class_id; const char *class_name() {return class_id;}; +#if FLTK_ABI_VERSION >= 10304 || defined(FL_DOXYGEN) + Fl_Image_Surface(int w, int h, int highres = 0); +#else + Fl_Image_Surface(int w, int h, int highres); Fl_Image_Surface(int w, int h); +#endif ~Fl_Image_Surface(); void set_current(); void draw(Fl_Widget*, int delta_x = 0, int delta_y = 0); void draw_decorated_window(Fl_Window* win, int delta_x = 0, int delta_y = 0); Fl_RGB_Image *image(); + Fl_Shared_Image *highres_image(); }; #ifdef __APPLE__ diff --git a/src/Fl_Image_Surface.cxx b/src/Fl_Image_Surface.cxx index 91b2b5855..7bbdab8e7 100644 --- a/src/Fl_Image_Surface.cxx +++ b/src/Fl_Image_Surface.cxx @@ -20,19 +20,43 @@ #include #include +#ifdef __APPLE__ +class Fl_Quartz_Scaled_Graphics_Driver_ : public Fl_Quartz_Graphics_Driver { +protected: + virtual void push_clip(int x, int y, int w, int h) { + CGContextRestoreGState(fl_gc); + CGContextSaveGState(fl_gc); + CGContextTranslateCTM(fl_gc, 0, CGBitmapContextGetHeight(fl_gc)/2); + CGContextScaleCTM(fl_gc, 1.0f, -1.0f); + CGContextClipToRect(fl_gc, CGRectMake(x, y, w, h)); + } + virtual void pop_clip() { + CGContextRestoreGState(fl_gc); + CGContextSaveGState(fl_gc); + CGContextTranslateCTM(fl_gc, 0, CGBitmapContextGetHeight(fl_gc)/2); + CGContextScaleCTM(fl_gc, 1.0f, -1.0f); + } +}; +#endif const char *Fl_Image_Surface::class_id = "Fl_Image_Surface"; -/** The constructor. - \param w and \param h give the size in pixels of the resulting image. - */ -Fl_Image_Surface::Fl_Image_Surface(int w, int h) : Fl_Surface_Device(NULL) { +void Fl_Image_Surface::prepare_(int w, int h, int highres) { width = w; height = h; +#if FL_ABI_VERSION < 10304 + highres = 0; +#endif #ifdef __APPLE__ - offscreen = fl_create_offscreen(w, h); - helper = new Fl_Quartz_Flipped_Surface_(width, height); + offscreen = fl_create_offscreen(highres ? 2*w : w, highres ? 2*h : h); + helper = new Fl_Quartz_Flipped_Surface_(w, h); + if (highres) { + delete helper->driver(); + helper->driver(new Fl_Quartz_Scaled_Graphics_Driver_); + CGContextScaleCTM(offscreen, 2, 2); + } driver(helper->driver()); + CGContextSetShouldAntialias(offscreen, false); CGContextSaveGState(offscreen); CGContextTranslateCTM(offscreen, 0, height); CGContextScaleCTM(offscreen, 1.0f, -1.0f); @@ -55,6 +79,24 @@ Fl_Image_Surface::Fl_Image_Surface(int w, int h) : Fl_Surface_Device(NULL) { #endif } +/** Constructor with optional high resolution. + \param w and \param h give the size in pixels of the resulting image. + \param highres if non-zero, the surface pixel size is twice as high and wide as w and h, + which is useful to draw it later on a high resolution display (e.g., retina display). + This is implemented for the Mac OS platform only. + If \p highres is non-zero, use Fl_Image_Surface::highres_image() to get the image data. + \version 1.3.4 and requires compilation with -DFL_ABI_VERSION=10304 (1.3.3 without the highres parameter) + */ +Fl_Image_Surface::Fl_Image_Surface(int w, int h, int highres) : Fl_Surface_Device(NULL) { + prepare_(w, h, highres); +} +#if FLTK_ABI_VERSION < 10304 +Fl_Image_Surface::Fl_Image_Surface(int w, int h) : Fl_Surface_Device(NULL) { + prepare_(w, h, 0); +} +#endif + + /** The destructor. */ Fl_Image_Surface::~Fl_Image_Surface() { @@ -75,14 +117,19 @@ Fl_Image_Surface::~Fl_Image_Surface() { /** Returns an image made of all drawings sent to the Fl_Image_Surface object. The returned object contains its own copy of the RGB data. + Prefer Fl_Image_Surface::highres_image() if the surface was + constructed with the highres option on. */ Fl_RGB_Image* Fl_Image_Surface::image() { unsigned char *data; - int depth = 3, ld = 0; + int W = width, H = height; #ifdef __APPLE__ CGContextFlush(offscreen); - data = fl_read_image(NULL, 0, 0, width, height, 0); + W = CGBitmapContextGetWidth(offscreen); + H = CGBitmapContextGetHeight(offscreen); + Fl_X::set_high_resolution(0); + data = fl_read_image(NULL, 0, 0, W, H, 0); fl_gc = 0; #elif defined(WIN32) fl_pop_clip(); @@ -98,11 +145,24 @@ Fl_RGB_Image* Fl_Image_Surface::image() fl_window = pre_window; previous->set_current(); #endif - Fl_RGB_Image *image = new Fl_RGB_Image(data, width, height, depth, ld); + Fl_RGB_Image *image = new Fl_RGB_Image(data, W, H); image->alloc_array = 1; return image; } +/** Returns a possibly high resolution image made of all drawings sent to the Fl_Image_Surface object. + The Fl_Image_Surface object should have been constructed with Fl_Image_Surface(W, H, 1). + The returned image is scaled to a size of WxH drawing units and may have a pixel size twice as wide and high. + The returned object should be deallocated with Fl_Shared_Image::release() after use. + \version 1.3.4 and requires compilation with -DFL_ABI_VERSION=10304 + */ +Fl_Shared_Image* Fl_Image_Surface::highres_image() +{ + Fl_Shared_Image *s_img = Fl_Shared_Image::get(image()); + s_img->scale(width, height); + return s_img; +} + /** Draws a widget in the image surface \param widget any FLTK widget (e.g., standard, custom, window, GL view) to draw in the image @@ -120,6 +180,7 @@ void Fl_Image_Surface::set_current() #if defined(__APPLE__) fl_gc = offscreen; fl_window = 0; Fl_Surface_Device::set_current(); + Fl_X::set_high_resolution( CGBitmapContextGetWidth(offscreen) > width ); #elif defined(WIN32) _sgc=fl_gc; _sw=fl_window; diff --git a/src/Fl_cocoa.mm b/src/Fl_cocoa.mm index 30b254d25..045337994 100644 --- a/src/Fl_cocoa.mm +++ b/src/Fl_cocoa.mm @@ -3379,9 +3379,31 @@ void Fl_X::q_begin_image(CGRect &rect, int cx, int cy, int w, int h) { r2.origin.x -= 0.5f; r2.origin.y -= 0.5f; CGContextClipToRect(fl_gc, r2); - // move graphics context to origin of vertically reversed image + // move graphics context to origin of vertically reversed image + // The 0.5 here cancels the 0.5 offset present in Quartz graphics contexts. + // Thus, image and surface pixels are in phase if there's no scaling. + // Below, we handle x2 and /2 scalings that occur when drawing to + // a double-resolution bitmap, and when drawing a double-resolution bitmap to display. CGContextTranslateCTM(fl_gc, rect.origin.x - cx - 0.5, rect.origin.y - cy + h - 0.5); CGContextScaleCTM(fl_gc, 1, -1); + CGAffineTransform at = CGContextGetCTM(fl_gc); + if (at.a == at.d && at.b == 0 && at.c == 0) { // proportional scaling, no rotation + // phase image with display pixels + CGFloat deltax = 0, deltay = 0; + if (at.a == 2) { // make .tx and .ty have even values + deltax = (at.tx/2 - round(at.tx/2)); + deltay = (at.ty/2 - round(at.ty/2)); + } else if (at.a == 0.5) { + if (Fl_Display_Device::high_resolution()) { // make .tx and .ty have int or half-int values + deltax = (at.tx*2 - round(at.tx*2)); + deltay = (at.ty*2 - round(at.ty*2)); + } else { // make .tx and .ty have integral values + deltax = (at.tx - round(at.tx))*2; + deltay = (at.ty - round(at.ty))*2; + } + } + CGContextTranslateCTM(fl_gc, -deltax, -deltay); + } rect.origin.x = rect.origin.y = 0; rect.size.width = w; rect.size.height = h; @@ -4403,7 +4425,10 @@ void Fl_X::draw_layer_to_context(void *layer, CGContextRef gc, int w, int h) Fl_X::clip_to_rounded_corners(gc, w, h); CGContextSetRGBFillColor(gc, .79, .79, .79, 1.); // equiv. to FL_DARK1 CGContextFillRect(gc, CGRectMake(0, 0, w, h)); + CGContextSaveGState(gc); + CGContextSetShouldAntialias(gc, true); [(CALayer*)layer renderInContext:gc]; // 10.5 + CGContextRestoreGState(gc); #endif } diff --git a/test/device.cxx b/test/device.cxx index a1c83a53f..c0fec4a1e 100644 --- a/test/device.cxx +++ b/test/device.cxx @@ -563,13 +563,13 @@ void copy(Fl_Widget *, void *data) { H = target->h(); decorated = 0; } - rgb_surf = new Fl_Image_Surface(W, H); + rgb_surf = new Fl_Image_Surface(W, H, 1); rgb_surf->set_current(); if (decorated) rgb_surf->draw_decorated_window(target->as_window()); else rgb_surf->draw(target); - Fl_Image *img = rgb_surf->image(); + Fl_Image *img = rgb_surf->highres_image(); delete rgb_surf; Fl_Display_Device::display_device()->set_current(); Fl_Window* g2 = new Fl_Window(img->w()+10, img->h()+10);