Add fl_write_jpeg function (STR 460) (#1405)
Build and Test / build-linux (push) Has been cancelled
Build and Test / build-wayland (push) Has been cancelled
Build and Test / build-macos (push) Has been cancelled
Build and Test / build-windows (push) Has been cancelled

This commit is contained in:
Matthias Melcher
2026-04-12 23:49:20 +02:00
committed by GitHub
parent 1ad5777c6a
commit 15448c45b0
4 changed files with 232 additions and 2 deletions
+6
View File
@@ -40,4 +40,10 @@ protected:
};
// Support functions to write JPEG image files (since 1.4.0)
FL_EXPORT int fl_write_jpeg(const char *filename, Fl_RGB_Image *img);
FL_EXPORT int fl_write_jpeg(const char *filename, const char *pixels, int w, int h, int d=3, int ld=0);
FL_EXPORT int fl_write_jpeg(const char *filename, const unsigned char *pixels, int w, int h, int d=3, int ld=0);
#endif
+1
View File
@@ -524,6 +524,7 @@ set(GLCPPFILES
set(IMGCPPFILES
fl_images_core.cxx
fl_write_png.cxx
fl_write_jpeg.cxx
Fl_BMP_Image.cxx
Fl_File_Icon2.cxx
Fl_GIF_Image.cxx
+197
View File
@@ -0,0 +1,197 @@
//
// Fl_JPEG_Image support functions for the Fast Light Tool Kit (FLTK).
//
// Copyright 2005-2025 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 <config.h>
#include <FL/Fl_JPEG_Image.H>
#include <FL/Fl_RGB_Image.H>
#include <FL/fl_utf8.h> // fl_fopen()
#include <stdio.h>
#include <stdlib.h> // malloc, free
extern "C" {
#ifdef HAVE_LIBJPEG
# include <jpeglib.h>
#endif // HAVE_LIBJPEG
} // extern "C"
/**
\file fl_write_jpeg.cxx
JPEG image support functions.
*/
/**
Write an RGB image to a JPEG image file.
Create a JPEG image file from an RGB image (Fl_RGB_Image).
The image file is always written with the original image size data_w()
and data_h(), even if the image has been scaled.
Image depth 1 (gray), 2 (gray + alpha), 3 (RGB), and 4 (RGBA) are supported.
For images with alpha channel (depth 2 or 4), the alpha component is ignored
and only the color data is written since JPEG does not support transparency.
\note Error handling is limited to basic error detection: library availability
and file opening errors.
\param[in] filename Output filename, extension should be '.jpg' or '.jpeg'
\param[in] img RGB image to be written
\return success (0) or error code: negative values are errors
\retval 0 success, file has been written
\retval -1 jpeg library not available
\retval -2 file open error
\retval -3 invalid image depth (must be 1, 2, 3, or 4)
\retval -4 memory allocation error
\see fl_write_jpeg(const char *, const char *, int, int, int, int)
*/
int fl_write_jpeg(const char *filename, Fl_RGB_Image *img) {
return fl_write_jpeg(filename,
img->data()[0],
img->data_w(),
img->data_h(),
img->d(),
img->ld());
}
/**
Write raw image data to a JPEG image file.
\see fl_write_jpeg(const char *filename, const char *pixels, int w, int h, int d, int ld)
*/
int fl_write_jpeg(const char *filename, const unsigned char *pixels, int w, int h, int d, int ld) {
return fl_write_jpeg(filename, (const char *)pixels, w, h, d, ld);
}
/**
Write raw image data to a JPEG image file.
For further restrictions and return values please see
fl_write_jpeg(const char *filename, Fl_RGB_Image *img).
\param[in] filename Output filename, extension should be '.jpg' or '.jpeg'
\param[in] pixels Image data
\param[in] w Image data width
\param[in] h Image data height
\param[in] d Image depth: 1 = GRAY, 2 = GRAY+alpha, 3 = RGB, 4 = RGBA
\param[in] ld Line delta: default (0) = w * d
\return success (0) or error code: negative values are errors
\see fl_write_jpeg(const char *filename, Fl_RGB_Image *img)
*/
int fl_write_jpeg(const char *filename, const char *pixels, int w, int h, int d, int ld) {
#ifdef HAVE_LIBJPEG
FILE *fp;
J_COLOR_SPACE color_space;
int out_d; // output depth (without alpha)
unsigned char *row_buf = NULL; // buffer for stripping alpha channel
// Validate depth: must be 1, 2, 3, or 4
if (d < 1 || d > 4) {
return -3;
}
if ((fp = fl_fopen(filename, "wb")) == NULL) {
return -2;
}
// Determine output depth and color space
// Strip alpha channel: depth 2 -> 1 (gray), depth 4 -> 3 (RGB)
switch (d) {
case 1:
case 2:
color_space = JCS_GRAYSCALE;
out_d = 1;
break;
default: // 3 or 4
color_space = JCS_RGB;
out_d = 3;
break;
}
if (ld == 0)
ld = w * d;
// Allocate buffer for stripping alpha if needed
int strip_alpha = (d == 2 || d == 4);
if (strip_alpha) {
row_buf = (unsigned char *)malloc(w * out_d);
if (row_buf == NULL) {
fclose(fp);
return -4;
}
}
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);
jpeg_stdio_dest(&cinfo, fp);
cinfo.image_width = w;
cinfo.image_height = h;
cinfo.input_components = out_d;
cinfo.in_color_space = color_space;
jpeg_set_defaults(&cinfo);
jpeg_set_quality(&cinfo, 95, TRUE); // Quality 95 is a good balance
jpeg_start_compress(&cinfo, TRUE);
JSAMPROW row_pointer;
const unsigned char *ptr = (const unsigned char *)pixels;
while (cinfo.next_scanline < cinfo.image_height) {
if (strip_alpha) {
// Strip alpha channel: copy only color components
const unsigned char *src = ptr;
unsigned char *dst = row_buf;
for (int x = 0; x < w; x++) {
for (int c = 0; c < out_d; c++) {
*dst++ = *src++;
}
src++; // skip alpha byte
}
row_pointer = (JSAMPROW)row_buf;
} else {
row_pointer = (JSAMPROW)ptr;
}
jpeg_write_scanlines(&cinfo, &row_pointer, 1);
ptr += ld;
}
jpeg_finish_compress(&cinfo);
jpeg_destroy_compress(&cinfo);
if (row_buf)
free(row_buf);
fclose(fp);
return 0;
#else
return -1;
#endif
}
+28 -2
View File
@@ -17,6 +17,7 @@
#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_JPEG_Image.H>
#include <FL/Fl_PNG_Image.H>
#include <FL/Fl_Shared_Image.H>
#include <FL/Fl_Text_Display.H>
@@ -45,6 +46,7 @@ Fl_Box *image_size; // to view image size
Fl_Text_Display *display; // to view clipboard text
Fl_Flex *flex; // flexible button layout
Fl_Button *save; // save PNG
Fl_Button *save_jpeg; // save JPEG
Fl_Check_Button *wrap; // wrap mode
Fl_RGB_Image *cl_img; // image from clipboard
@@ -96,9 +98,11 @@ public:
void layout() { // re-arrange buttons depending on tab
if (value() == display) { // text
save->hide();
save_jpeg->hide();
wrap->show();
} else { // image
save->show();
save_jpeg->show();
wrap->hide();
}
flex->layout();
@@ -181,7 +185,7 @@ void refresh_cb(Fl_Widget *, void *v) {
}
// "Save PNG" callback
void save_cb(Fl_Widget *wid, void *) {
void save_cb(Fl_Widget *, void *) {
if (cl_img && !cl_img->fail()) {
Fl_Native_File_Chooser fnfc;
fnfc.title("Please select a .png file");
@@ -198,6 +202,24 @@ void save_cb(Fl_Widget *wid, void *) {
}
}
// "Save JPEG" callback
void save_jpeg_cb(Fl_Widget *, void *) {
if (cl_img && !cl_img->fail()) {
Fl_Native_File_Chooser fnfc;
fnfc.title("Please select a .jpg file");
fnfc.type(Fl_Native_File_Chooser::BROWSE_SAVE_FILE);
fnfc.filter("JPEG\t*.jpg\n");
fnfc.options(Fl_Native_File_Chooser::SAVEAS_CONFIRM | Fl_Native_File_Chooser::USE_FILTER_EXT);
if (fnfc.show())
return;
const char *filename = fnfc.filename();
if (filename)
fl_write_jpeg(filename, cl_img);
} else {
fl_message("%s", "No image available");
}
}
// "wrap mode" callback (switch wrapping on/off)
void wrap_cb(Fl_Widget *w, void *d) {
auto display = (Fl_Text_Display *)d;
@@ -246,10 +268,14 @@ int main(int argc, char **argv) {
flex->fixed(refresh, 200);
refresh->callback(refresh_cb, (void *)tabs);
save = new Fl_Button(0, 0, 0, 0 , "Save PNG");
save = new Fl_Button(0, 0, 0, 0, "Save PNG");
flex->fixed(save, 120);
save->callback(save_cb);
save_jpeg = new Fl_Button(0, 0, 0, 0, "Save JPEG");
flex->fixed(save_jpeg, 120);
save_jpeg->callback(save_jpeg_cb);
wrap = new Fl_Check_Button(0, 0, 0, 0 , "wrap mode");
flex->fixed(wrap, 120);
wrap->callback(wrap_cb, display);