366 lines
14 KiB
C++
366 lines
14 KiB
C++
//
|
|
// Draw-to-image code for the Fast Light Tool Kit (FLTK).
|
|
//
|
|
// Copyright 1998-2021 by Bill Spitzak and others.
|
|
//
|
|
// This library is free software. Distribution and use rights are outlined in
|
|
// the file "COPYING" which should have been included with this file. If this
|
|
// file is missing or damaged, see the license at:
|
|
//
|
|
// https://www.fltk.org/COPYING.php
|
|
//
|
|
// Please see the following page on how to report bugs and issues:
|
|
//
|
|
// https://www.fltk.org/bugs.php
|
|
//
|
|
|
|
#include <FL/Fl_Image_Surface.H>
|
|
|
|
#include <FL/fl_draw.H> // necessary for FL_EXPORT fl_*_offscreen()
|
|
|
|
#include <stdlib.h> // realloc()
|
|
|
|
/** Constructor with optional high resolution.
|
|
\param w,h Width and height of the resulting image. The value of the \p high_res
|
|
parameter controls whether \p w and \p h are interpreted as pixels or FLTK units.
|
|
|
|
\param high_res If zero, the created image surface is sized at \p w x \p h pixels.
|
|
If non-zero, the pixel size of the created image surface depends on
|
|
the value of the display scale factor (see Fl::screen_scale(int)):
|
|
the resulting image has the same number of pixels as an area of the display of size
|
|
\p w x \p h expressed in FLTK units.
|
|
|
|
\param off If not null, the image surface is constructed around a pre-existing
|
|
Fl_Offscreen. The caller is responsible for both construction and destruction of this Fl_Offscreen object.
|
|
Is mostly intended for internal use by FLTK.
|
|
\version 1.3.4 (1.3.3 without the \p highres parameter)
|
|
*/
|
|
Fl_Image_Surface::Fl_Image_Surface(int w, int h, int high_res, Fl_Offscreen off) : Fl_Widget_Surface(NULL) {
|
|
platform_surface = Fl_Image_Surface_Driver::newImageSurfaceDriver(w, h, high_res, off);
|
|
platform_surface->image_surface_ = this;
|
|
driver(platform_surface->driver());
|
|
}
|
|
|
|
|
|
/** The destructor. */
|
|
Fl_Image_Surface::~Fl_Image_Surface() {
|
|
if (is_current()) platform_surface->end_current();
|
|
delete platform_surface;
|
|
}
|
|
|
|
void Fl_Image_Surface::origin(int x, int y) {platform_surface->origin(x, y);}
|
|
|
|
void Fl_Image_Surface::origin(int *x, int *y) {
|
|
if (platform_surface) platform_surface->origin(x, y);
|
|
}
|
|
|
|
void Fl_Image_Surface::set_current() {
|
|
if (platform_surface) platform_surface->set_current();
|
|
}
|
|
|
|
bool Fl_Image_Surface::is_current() {
|
|
return surface() == platform_surface;
|
|
}
|
|
|
|
void Fl_Image_Surface::translate(int x, int y) {
|
|
if (platform_surface) platform_surface->translate(x, y);
|
|
}
|
|
|
|
void Fl_Image_Surface::untranslate() {
|
|
if (platform_surface) platform_surface->untranslate();
|
|
}
|
|
|
|
/** Returns the Fl_Offscreen object associated to the image surface.
|
|
The returned Fl_Offscreen object is deleted when the Fl_Image_Surface object is deleted,
|
|
unless the Fl_Image_Surface was constructed with non-null Fl_Offscreen argument.
|
|
*/
|
|
Fl_Offscreen Fl_Image_Surface::offscreen() {
|
|
return platform_surface ? platform_surface->offscreen : (Fl_Offscreen)0;
|
|
}
|
|
|
|
int Fl_Image_Surface::printable_rect(int *w, int *h) {return platform_surface->printable_rect(w, h);}
|
|
|
|
/**
|
|
\cond DriverDev
|
|
\addtogroup DriverDeveloper
|
|
\{
|
|
*/
|
|
int Fl_Image_Surface_Driver::printable_rect(int *w, int *h) {
|
|
*w = width; *h = height;
|
|
return 0;
|
|
}
|
|
|
|
// used by the Windows and X11(no Cairo) platforms
|
|
void Fl_Image_Surface_Driver::copy_with_mask(Fl_RGB_Image* mask, uchar *dib_dst,
|
|
uchar *dib_src, int line_size,
|
|
bool bottom_to_top) {
|
|
int w = mask->data_w(), h = mask->data_h();
|
|
for (int i = 0; i < h; i++) {
|
|
const uchar* alpha = (const uchar*)mask->array +
|
|
(bottom_to_top ? (h-i-1) : i) * w;
|
|
uchar *src = dib_src + i * line_size;
|
|
uchar *dst = dib_dst + i * line_size;
|
|
for (int j = 0; j < w; j++) {
|
|
// mix src and dst into dst weighted by mask pixel's value
|
|
uchar u = *alpha++, v = 255 - u;
|
|
*dst = ((*dst) * v + (*src) * u)/255;
|
|
dst++; src++;
|
|
*dst = ((*dst) * v + (*src) * u)/255;
|
|
dst++; src++;
|
|
*dst = ((*dst) * v + (*src) * u)/255;
|
|
dst++; src++;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
Fl_RGB_Image *Fl_Image_Surface_Driver::RGB3_to_RGB1(const Fl_RGB_Image *rgb3, int W, int H) {
|
|
bool need_copy = false;
|
|
if (W != rgb3->data_w() || H != rgb3->data_h()) {
|
|
rgb3 = (Fl_RGB_Image*)rgb3->copy(W, H);
|
|
need_copy = true;
|
|
}
|
|
uchar *data = new uchar[W * H];
|
|
int i, j, ld = rgb3->ld();
|
|
if (!ld) ld = 3 * W;
|
|
uchar *p = data;
|
|
for (i = 0; i < H; i++) {
|
|
const uchar* alpha = rgb3->array + i * ld;
|
|
for (j = 0; j < W; j++) {
|
|
*p++ = (*alpha + *(alpha+1) + *(alpha+2)) / 3;
|
|
alpha += 3;
|
|
}
|
|
}
|
|
Fl_RGB_Image *rgb1 = new Fl_RGB_Image(data, W, H, 1);
|
|
rgb1->alloc_array = 1;
|
|
if (need_copy) delete rgb3;
|
|
return rgb1;
|
|
}
|
|
|
|
/**
|
|
\}
|
|
\endcond
|
|
*/
|
|
|
|
/** Returns a depth-3 image made of all drawings sent to the Fl_Image_Surface object.
|
|
The returned object contains its own copy of the RGB data;
|
|
the caller is responsible for deleting it.
|
|
|
|
\see Fl_Image_Surface::mask(Fl_RGB_Image*)
|
|
*/
|
|
Fl_RGB_Image *Fl_Image_Surface::image() {
|
|
bool need_push = (Fl_Surface_Device::surface() != platform_surface);
|
|
if (need_push) Fl_Surface_Device::push_current(platform_surface);
|
|
Fl_RGB_Image *img = platform_surface->image();
|
|
if (need_push) Fl_Surface_Device::pop_current();
|
|
img->scale(platform_surface->width, platform_surface->height, 1, 1);
|
|
return img;
|
|
}
|
|
|
|
/** 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 Fl_Shared_Image object is scaled to a size of WxH FLTK units and may have a
|
|
pixel size larger than these values.
|
|
The returned object should be deallocated with Fl_Shared_Image::release() after use.
|
|
\deprecated Use image() instead.
|
|
\version 1.4 (1.3.4 for MacOS platform only)
|
|
*/
|
|
Fl_Shared_Image* Fl_Image_Surface::highres_image()
|
|
{
|
|
if (!platform_surface) return NULL;
|
|
Fl_Shared_Image *s_img = Fl_Shared_Image::get(image());
|
|
int width, height;
|
|
platform_surface->printable_rect(&width, &height);
|
|
s_img->scale(width, height, 1, 1);
|
|
return s_img;
|
|
}
|
|
|
|
// Allows to delete the Fl_Image_Surface object while keeping its underlying Fl_Offscreen
|
|
Fl_Offscreen Fl_Image_Surface::get_offscreen_before_delete_() {
|
|
Fl_Offscreen keep = platform_surface->offscreen;
|
|
platform_surface->offscreen = 0;
|
|
return keep;
|
|
}
|
|
|
|
/** Adapts the Fl_Image_Surface object to the new value of the GUI scale factor.
|
|
The Fl_Image_Surface object must not be the current drawing surface.
|
|
This function is useful only for an object constructed with non-zero \p high_res parameter.
|
|
\version 1.4
|
|
*/
|
|
void Fl_Image_Surface::rescale() {
|
|
Fl_RGB_Image *rgb = image();
|
|
int w, h;
|
|
printable_rect(&w, &h);
|
|
delete platform_surface;
|
|
platform_surface = Fl_Image_Surface_Driver::newImageSurfaceDriver(w, h, 1, 0);
|
|
Fl_Surface_Device::push_current(this);
|
|
rgb->draw(0,0);
|
|
Fl_Surface_Device::pop_current();
|
|
delete rgb;
|
|
}
|
|
|
|
|
|
/** Defines a mask applied to drawings made after use of this function.
|
|
The mask is an Fl_RGB_Image made of a white scene drawn on a solid black
|
|
background; the drawable part of the image surface is reduced to the white areas of the mask
|
|
after this member function gets called. If necessary, the \p mask image is internally
|
|
replaced by a copy resized to the surface's pixel size.
|
|
Overall, the image returned by Fl_Image_Surface::image() contains all drawings made
|
|
until the mask() method assigned a mask, at which point subsequent drawing operations
|
|
to the image surface were passed through the white areas of the mask.
|
|
On some platforms, shades of gray in the mask image control the blending of
|
|
foreground and background pixels; mask pixels closer in color to white produce image pixels
|
|
closer to the image surface pixel, those closer to black produce image pixels closer to what the
|
|
image surface pixel was before the call to mask().
|
|
|
|
The mask is easily constructed using an Fl_Image_Surface object,
|
|
drawing white areas on a black background there, and calling Fl_Image_Surface::image().
|
|
\param mask A depth-3 image determining the drawable areas of the image surface.
|
|
The \p mask object is not used after return from this member function.
|
|
\note
|
|
- The image surface must not be the current drawing surface when this function
|
|
gets called.
|
|
- The mask can have any size but is best when it has the size of the image surface.
|
|
- It's possible to use several masks in succession on the same image surface provided
|
|
member function Fl_Image_Surface::image() is called between successive calls to
|
|
Fl_Image_Surface::mask(const Fl_RGB_Image*).
|
|
|
|
Example of procedure to construct a masked image:
|
|
\code
|
|
int W = …, H = …; // width and height of the image under construction
|
|
Fl_Image_Surface *surf = new Fl_Image_Surface(W, H, 1);
|
|
// first, construct the mask
|
|
Fl_Surface_Device::push_current(surf);
|
|
fl_color(FL_BLACK); // draw a black background
|
|
fl_rectf(0, 0, W, H);
|
|
fl_color(FL_WHITE); // next, draw in white what the mask should not filter out
|
|
fl_pie(0, 0, W, H, 0, 360); // here, an ellipse with axes lengths WxH
|
|
Fl_RGB_Image *mask = surf->image(); // get the mask
|
|
// second, draw the image background
|
|
fl_color(FL_YELLOW); // here, draw a yellow background
|
|
fl_rectf(0, 0, W, H);
|
|
// third, apply the mask
|
|
Fl_Surface_Device::pop_current();
|
|
surf->mask(mask);
|
|
delete mask; // the mask image can be safely deleted at this point
|
|
Fl_Surface_Device::push_current(surf);
|
|
// fourth, draw the image foreground, part of which will be filtered out by the mask
|
|
surf->draw(widget, 0, 0); // here the foreground is a drawn widget
|
|
// fifth, get the final result, masked_image, as a depth-3 Fl_RGB_Image
|
|
Fl_RGB_Image *masked_image = surf->image();
|
|
// Only the part of the foreground, here a drawn widget, that has not been
|
|
// filtered out by the mask, here the white ellipse, is in masked_image;
|
|
// the background, here solid yellow, shows up in the remaining areas of masked_image.
|
|
Fl_Surface_Device::pop_current();
|
|
delete surf;
|
|
\endcode
|
|
|
|
\since 1.4.0
|
|
*/
|
|
void Fl_Image_Surface::mask(const Fl_RGB_Image *mask) {
|
|
platform_surface->mask(mask);
|
|
}
|
|
|
|
|
|
// implementation of the fl_XXX_offscreen() functions
|
|
|
|
static Fl_Image_Surface **offscreen_api_surface = NULL;
|
|
static int count_offscreens = 0;
|
|
|
|
static int find_slot(void) { // return an available slot to memorize an Fl_Image_Surface object
|
|
static int max = 0;
|
|
for (int num = 0; num < count_offscreens; num++) {
|
|
if (!offscreen_api_surface[num]) return num;
|
|
}
|
|
if (count_offscreens >= max) {
|
|
max += 20;
|
|
offscreen_api_surface = (Fl_Image_Surface**)realloc(offscreen_api_surface, max * sizeof(void *));
|
|
}
|
|
return count_offscreens++;
|
|
}
|
|
|
|
/** \addtogroup fl_drawings
|
|
@{
|
|
*/
|
|
|
|
/**
|
|
Creation of an offscreen graphics buffer.
|
|
\param w,h width and height in FLTK units of the buffer.
|
|
\return the created graphics buffer.
|
|
|
|
The pixel size of the created graphics buffer is equal to the number of pixels
|
|
in an area of the screen containing the current window sized at \p w,h FLTK units.
|
|
This pixel size varies with the value of the scale factor of this screen.
|
|
\note Work with the fl_XXX_offscreen() functions is equivalent to work with
|
|
an Fl_Image_Surface object, as follows :
|
|
<table>
|
|
<tr> <th>Fl_Offscreen-based approach</th><th>Fl_Image_Surface-based approach</th> </tr>
|
|
<tr> <td>Fl_Offscreen off = fl_create_offscreen(w, h)</td><td>Fl_Image_Surface *surface = new Fl_Image_Surface(w, h, 1)</td> </tr>
|
|
<tr> <td>fl_begin_offscreen(off)</td><td>Fl_Surface_Device::push_current(surface)</td> </tr>
|
|
<tr> <td>fl_end_offscreen()</td><td>Fl_Surface_Device::pop_current()</td> </tr>
|
|
<tr> <td>fl_copy_offscreen(x,y,w,h, off, sx,sy)</td><td>fl_copy_offscreen(x,y,w,h, surface->offscreen(), sx,sy)</td> </tr>
|
|
<tr> <td>fl_rescale_offscreen(off)</td><td>surface->rescale()</td> </tr>
|
|
<tr> <td>fl_delete_offscreen(off)</td><td>delete surface</td> </tr>
|
|
</table>
|
|
*/
|
|
Fl_Offscreen fl_create_offscreen(int w, int h) {
|
|
int rank = find_slot();
|
|
offscreen_api_surface[rank] = new Fl_Image_Surface(w, h, 1/*high_res*/);
|
|
return offscreen_api_surface[rank]->offscreen();
|
|
}
|
|
|
|
/** Deletion of an offscreen graphics buffer.
|
|
\param ctx the buffer to be deleted.
|
|
\note The \p ctx argument must have been created by fl_create_offscreen().
|
|
*/
|
|
void fl_delete_offscreen(Fl_Offscreen ctx) {
|
|
if (!ctx) return;
|
|
for (int i = 0; i < count_offscreens; i++) {
|
|
if (offscreen_api_surface[i] && offscreen_api_surface[i]->offscreen() == ctx) {
|
|
delete offscreen_api_surface[i];
|
|
offscreen_api_surface[i] = NULL;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Send all subsequent drawing commands to this offscreen buffer.
|
|
\param ctx the offscreen buffer.
|
|
\note The \p ctx argument must have been created by fl_create_offscreen().
|
|
*/
|
|
void fl_begin_offscreen(Fl_Offscreen ctx) {
|
|
for (int i = 0; i < count_offscreens; i++) {
|
|
if (offscreen_api_surface[i] && offscreen_api_surface[i]->offscreen() == ctx) {
|
|
Fl_Surface_Device::push_current(offscreen_api_surface[i]);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Quit sending drawing commands to the current offscreen buffer.
|
|
*/
|
|
void fl_end_offscreen() {
|
|
Fl_Surface_Device::pop_current();
|
|
}
|
|
|
|
/** Adapts an offscreen buffer to a changed value of the scale factor.
|
|
The \p ctx argument must have been created by fl_create_offscreen()
|
|
and the calling context must not be between fl_begin_offscreen() and fl_end_offscreen().
|
|
The graphical content of the offscreen is preserved. The current scale factor
|
|
value is given by <tt>Fl_Graphics_Driver::default_driver().scale()</tt>.
|
|
\version 1.4
|
|
*/
|
|
void fl_rescale_offscreen(Fl_Offscreen &ctx) {
|
|
int i;
|
|
for (i = 0; i < count_offscreens; i++) {
|
|
if (offscreen_api_surface[i] && offscreen_api_surface[i]->offscreen() == ctx) {
|
|
break;
|
|
}
|
|
}
|
|
if (i >= count_offscreens) return;
|
|
offscreen_api_surface[i]->rescale();
|
|
ctx = offscreen_api_surface[i]->offscreen();
|
|
}
|
|
|
|
/** @} */
|