diff options
| author | Matthias Melcher <github@matthiasm.com> | 2025-03-16 17:16:12 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-03-16 17:16:12 -0400 |
| commit | 51a55bc73660f64e8f4b32b8b4d3858f2a786f7b (patch) | |
| tree | 122ad9f838fcf8f61ed7cf5fa031e8ed69817e10 /fluid/app/Image_Asset.cxx | |
| parent | 13a7073a1e007ce5b71ef70bced1a9b15158820d (diff) | |
Fluid: restructuring and rejuvenation of the source code.
* Add classes for application and project
* Removed all globals from Fluid.h
* Extracting args and project history into their own classes
* Moving globals into Application class
* Initialize values inside headers for some classes.
* Undo functionality wrapped in a class inside Project.
* File reader and writer are now linked to a project.
* Avoid global project access
* Nodes (former Types) will be managed by a new Tree class.
* Removed static members (hidden globals) form Node/Fl_Type.
* Adding Tree iterator.
* Use nullptr instead of 0, NULL, or 0L
* Renamed Fl_..._Type to ..._Node, FL_OVERRIDE -> override
* Renaming ..._type to ...::prototype
* Splitting Widget Panel into multiple files.
* Moved callback code into widget panel file.
* Cleaning up Fluid_Image -> Image_asset
* Moving Fd_Snap_Action into new namespace fld::app::Snap_Action etc.
* Moved mergeback into proj folder.
* `enum ID` is now `enum class Type`.
Diffstat (limited to 'fluid/app/Image_Asset.cxx')
| -rw-r--r-- | fluid/app/Image_Asset.cxx | 532 |
1 files changed, 532 insertions, 0 deletions
diff --git a/fluid/app/Image_Asset.cxx b/fluid/app/Image_Asset.cxx new file mode 100644 index 000000000..a001f2291 --- /dev/null +++ b/fluid/app/Image_Asset.cxx @@ -0,0 +1,532 @@ +// +// Image Helper code for the Fast Light Tool Kit (FLTK). +// +// Copyright 1998-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 "app/Image_Asset.h" + +#include "Fluid.h" +#include "io/Project_Reader.h" +#include "io/Project_Writer.h" +#include "io/Code_Writer.h" +#include "nodes/Group_Node.h" +#include "nodes/Window_Node.h" +#include "tools/filename.h" + +#include <FL/Fl.H> +#include <FL/Fl_Widget.H> +#include <FL/Fl_Window.H> +#include <FL/fl_string_functions.h> +#include <FL/fl_utf8.h> // fl_fopen() +#include <FL/Fl_File_Chooser.H> +#include <FL/Fl_SVG_Image.H> +#include <FL/Fl_Anim_GIF_Image.H> +#include "../src/flstring.h" + +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> +#include <stdarg.h> +#include <map> +#include <string> + + +/** + \brief A map of all image assets. + \todo This is a global variable, but should be associated + with a project instead. + */ +static std::map<std::string, Image_Asset*> image_asset_map; + + +/** + \brief Write the contents of the image file as binary source code. + + Write the contents of the image file as C++ code, so the image is available in + the target app in the original binary format, for example: + ``` + { 1, 2, 3, ...} + ``` + \param f Write code to this C++ source code file + \param fmt short name of file contents for error message + \return 0 if the file could not be opened or read + */ +size_t Image_Asset::write_static_binary(fld::io::Code_Writer& f, const char* fmt) { + size_t nData = 0; + Fluid.proj.enter_project_dir(); + FILE *in = fl_fopen(filename(), "rb"); + Fluid.proj.leave_project_dir(); + if (!in) { + write_file_error(f, fmt); + return 0; + } else { + fseek(in, 0, SEEK_END); + nData = ftell(in); + fseek(in, 0, SEEK_SET); + if (nData) { + char *data = (char*)calloc(nData, 1); + if (fread(data, nData, 1, in)==0) { /* ignore */ } + f.write_cdata(data, (int)nData); + free(data); + } + fclose(in); + } + return nData; +} + + +/** + \brief Write the contents of the image file as text with escaped special characters. + + This function is only useful for writing out image formats that are ASCII text + based, like svg and pixmaps. Other formats should use write_static_binary(). + + \param f Write code to this C++ source code file + \param fmt short name of file contents for error message + \return 0 if the file could not be opened or read + */ +size_t Image_Asset::write_static_text(fld::io::Code_Writer& f, const char* fmt) { + size_t nData = 0; + Fluid.proj.enter_project_dir(); + FILE *in = fl_fopen(filename(), "rb"); + Fluid.proj.leave_project_dir(); + if (!in) { + write_file_error(f, fmt); + return 0; + } else { + fseek(in, 0, SEEK_END); + nData = ftell(in); + fseek(in, 0, SEEK_SET); + if (nData) { + char *data = (char*)calloc(nData+1, 1); + if (fread(data, nData, 1, in)==0) { /* ignore */ } + f.write_cstring(data, (int)nData); + free(data); + } + fclose(in); + } + return nData; +} + + +/** + \brief Write the contents of the image file as uncompressed RGB. + + Write source code that generates the uncompressed RGB image data at compile + time, and an initializer that creates the image at run time. + + \todo If the ld() value is not 0 and not d()*w(), the image data is not + written correctly. There is no check if the data is actually in RGB format. + + \param f Write code to this C++ source code file + \param fmt short name of file contents for error message + \return 0 if the file could not be opened or read + */ +void Image_Asset::write_static_rgb(fld::io::Code_Writer& f, const char* idata_name) { + // Write image data... + f.write_c("\n"); + f.write_c_once("#include <FL/Fl_Image.H>\n"); + f.write_c("static const unsigned char %s[] =\n", idata_name); + const int extra_data = image_->ld() ? (image_->ld()-image_->w()*image_->d()) : 0; + f.write_cdata(image_->data()[0], (image_->w() * image_->d() + extra_data) * image_->h()); + f.write_c(";\n"); + write_initializer(f, "Fl_RGB_Image", "%s, %d, %d, %d, %d", idata_name, image_->w(), image_->h(), image_->d(), image_->ld()); +} + + +/** + \brief Write the static image data into the source file. + + Write source code that generates the image data at compile time, and an + initializer that creates the image at run time. + + If \p compressed is set, write the original image format, which requires + linking the matching image reader at runtime, or if we want to store the raw + uncompressed pixels, which makes images fast, needs no reader, but takes a + lot of memory (current default for PNG) + + \param f Write code to this C++ source code file + \param compressed write data in the original compressed file format + */ +void Image_Asset::write_static(fld::io::Code_Writer& f, int compressed) { + if (!image_) return; + const char *idata_name = f.unique_id(this, "idata", fl_filename_name(filename()), nullptr); + initializer_function_ = f.unique_id(this, "image", fl_filename_name(filename()), nullptr); + + if (is_animated_gif_) { + // Write animated gif image data... + f.write_c("\n"); + f.write_c_once("#include <FL/Fl_Anim_GIF_Image.H>\n"); + f.write_c("static const unsigned char %s[] =\n", idata_name); + size_t nData = write_static_binary(f, "AnimGIF"); + f.write_c(";\n"); + write_initializer(f, "Fl_Anim_GIF_Image", "\"%s\", %s, %d", fl_filename_name(filename()), idata_name, nData); + } else if (compressed && fl_ascii_strcasecmp(fl_filename_ext(filename()), ".gif")==0) { + // Write gif image data... + f.write_c("\n"); + f.write_c_once("#include <FL/Fl_GIF_Image.H>\n"); + f.write_c("static const unsigned char %s[] =\n", idata_name); + size_t nData = write_static_binary(f, "GIF"); + f.write_c(";\n"); + write_initializer(f, "Fl_GIF_Image", "\"%s\", %s, %d", fl_filename_name(filename()), idata_name, nData); + } else if (compressed && fl_ascii_strcasecmp(fl_filename_ext(filename()), ".bmp")==0) { + // Write bmp image data... + f.write_c("\n"); + f.write_c_once("#include <FL/Fl_BMP_Image.H>\n"); + f.write_c("static const unsigned char %s[] =\n", idata_name); + size_t nData = write_static_binary(f, "BMP"); + f.write_c(";\n"); + write_initializer(f, "Fl_BMP_Image", "\"%s\", %s, %d", fl_filename_name(filename()), idata_name, nData); + } else if (image_->count() > 1) { + // Write Pixmap data... + f.write_c("\n"); + f.write_c_once("#include <FL/Fl_Pixmap.H>\n"); + f.write_c("static const char *%s[] = {\n", idata_name); + f.write_cstring(image_->data()[0], (int)strlen(image_->data()[0])); + + int i; + int ncolors, chars_per_color; + sscanf(image_->data()[0], "%*d%*d%d%d", &ncolors, &chars_per_color); + + if (ncolors < 0) { + f.write_c(",\n"); + f.write_cstring(image_->data()[1], ncolors * -4); + i = 2; + } else { + for (i = 1; i <= ncolors; i ++) { + f.write_c(",\n"); + f.write_cstring(image_->data()[i], (int)strlen(image_->data()[i])); + } + } + for (; i < image_->count(); i ++) { + f.write_c(",\n"); + f.write_cstring(image_->data()[i], image_->w() * chars_per_color); + } + f.write_c("\n};\n"); + write_initializer(f, "Fl_Pixmap", "%s", idata_name); + } else if (image_->d() == 0) { + // Write Bitmap data... + f.write_c("\n"); + f.write_c_once("#include <FL/Fl_Bitmap.H>\n"); + f.write_c("static const unsigned char %s[] =\n", idata_name); + f.write_cdata(image_->data()[0], ((image_->w() + 7) / 8) * image_->h()); + f.write_c(";\n"); + write_initializer(f, "Fl_Bitmap", "%s, %d, %d, %d", idata_name, ((image_->w() + 7) / 8) * image_->h(), image_->w(), image_->h()); + } else if (compressed && fl_ascii_strcasecmp(fl_filename_ext(filename()), ".jpg")==0) { + // Write jpeg image data... + f.write_c("\n"); + f.write_c_once("#include <FL/Fl_JPEG_Image.H>\n"); + f.write_c("static const unsigned char %s[] =\n", idata_name); + size_t nData = write_static_binary(f, "JPEG"); + f.write_c(";\n"); + write_initializer(f, "Fl_JPEG_Image", "\"%s\", %s, %d", fl_filename_name(filename()), idata_name, nData); + } else if (compressed && fl_ascii_strcasecmp(fl_filename_ext(filename()), ".png")==0) { + // Write png image data... + f.write_c("\n"); + f.write_c_once("#include <FL/Fl_PNG_Image.H>\n"); + f.write_c("static const unsigned char %s[] =\n", idata_name); + size_t nData = write_static_binary(f, "PNG"); + f.write_c(";\n"); + write_initializer(f, "Fl_PNG_Image", "\"%s\", %s, %d", fl_filename_name(filename()), idata_name, nData); + } +#ifdef FLTK_USE_SVG + else if (fl_ascii_strcasecmp(fl_filename_ext(filename()), ".svg")==0 || fl_ascii_strcasecmp(fl_filename_ext(filename()), ".svgz")==0) { + bool gzipped = (strcmp(fl_filename_ext(filename()), ".svgz") == 0); + // Write svg image data... + if (compressed) { + f.write_c("\n"); + f.write_c_once("#include <FL/Fl_SVG_Image.H>\n"); + if (gzipped) { + f.write_c("static const unsigned char %s[] =\n", idata_name); + size_t nData = write_static_binary(f, "SVGZ"); + f.write_c(";\n"); + write_initializer(f, "Fl_SVG_Image", "\"%s\", %s, %ld", fl_filename_name(filename()), idata_name, nData); + } else { + f.write_c("static const char %s[] =\n", idata_name); + write_static_text(f, "SVG"); + f.write_c(";\n"); + write_initializer(f, "Fl_SVG_Image", "\"%s\", %s", fl_filename_name(filename()), idata_name); + } + } else { + // if FLUID runs from the command line, make sure that the image is not + // only loaded but also rasterized, so we can write the RGB image data + Fl_RGB_Image* rgb_image = nullptr; + Fl_SVG_Image* svg_image = nullptr; + if (image_->d()>0) + rgb_image = (Fl_RGB_Image*)image_->image(); + if (rgb_image) + svg_image = rgb_image->as_svg_image(); + if (svg_image) { + svg_image->resize(svg_image->w(), svg_image->h()); + write_static_rgb(f, idata_name); + } else { + write_file_error(f, "RGB_from_SVG"); + } + } + } +#endif // FLTK_USE_SVG + else { + write_static_rgb(f, idata_name); + } +} + + +/** + \brief Write a warning message to the generated code file that the image asset + can't be read. + + This writes a #warning directive to the generated code file which contains the + filename of the image asset and the error message as returned by strerror(errno). + The current working directory is also printed for debugging purposes. + \param f The C++ source code file to write the warning message to. + \param fmt The format string for the image file type. + */ +void Image_Asset::write_file_error(fld::io::Code_Writer& f, const char *fmt) { + f.write_c("#warning Cannot read %s file \"%s\": %s\n", fmt, filename(), strerror(errno)); + Fluid.proj.enter_project_dir(); + f.write_c("// Searching in path \"%s\"\n", fl_getcwd(nullptr, FL_PATH_MAX)); + Fluid.proj.leave_project_dir(); +} + + +/** + \brief Outputs code that loads and returns an Fl_Image. + + The generated code loads the image if it hasn't been loaded yet, and then + returns a pointer to the image. + + \code + static Fl_Image *'initializer_function_'() { + static Fl_Image *image = 0L; + if (!image) + image = new 'type_name'('product of format and remaining args'); + return image; + } + \endcode + + \param f Write the C++ code to this file. + \param image_class Name of the Fl_Image class, for example Fl_GIF_Image. + \param format Format string for additional parameters for the constructor. + */ +void Image_Asset::write_initializer(fld::io::Code_Writer& f, const char *image_class, const char *format, ...) { + va_list ap; + va_start(ap, format); + f.write_c("static Fl_Image *%s() {\n", initializer_function_.c_str()); + if (is_animated_gif_) + f.write_c("%sFl_GIF_Image::animate = true;\n", f.indent(1)); + f.write_c("%sstatic Fl_Image *image = 0L;\n", f.indent(1)); + f.write_c("%sif (!image)\n", f.indent(1)); + f.write_c("%simage = new %s(", f.indent(2), image_class); + f.vwrite_c(format, ap); + f.write_c(");\n"); + f.write_c("%sreturn image;\n", f.indent(1)); + f.write_c("}\n"); + va_end(ap); +} + + +/** + \brief Outputs code that attaches an image to an Fl_Widget or Fl_Menu_Item. + + The generated code will call the image initializer function and assign + the resulting image to the widget. + + \param f Write the C++ code to this file. + \param bind If true, use bind_image() instead of image(). + \param var Name of the Fl_Widget or Fl_Menu_Item to attach the image to. + \param inactive If true, use deimage() instead of image(). + */ +void Image_Asset::write_code(fld::io::Code_Writer& f, int bind, const char *var, int inactive) { + if (image_) { + f.write_c("%s%s->%s%s( %s() );\n", + f.indent(), + var, + bind ? "bind_" : "", + inactive ? "deimage" : "image", + initializer_function_.c_str()); + if (is_animated_gif_) + f.write_c("%s((Fl_Anim_GIF_Image*)(%s()))->canvas(%s, Fl_Anim_GIF_Image::DONT_RESIZE_CANVAS);\n", + f.indent(), + initializer_function_.c_str(), + var); + } +} + + +/** + \brief Outputs code that calls the image initializer function. + + The generated code calls the image initializer function, loading an image and + assigning it to a Fl_Menu_Item. + + \param f Write the C++ code to this file. + \param inactive Unused. + */ +void Image_Asset::write_inline(fld::io::Code_Writer& f, int inactive) { + (void)inactive; + if (image_) { + f.write_c("%s()", initializer_function_.c_str()); + } +} + + +/** + \brief Finds an image asset by filename. + + If the image asset has already been loaded, it is returned from the cache. + If the image asset has not been loaded, it is loaded from the file system. + If the image asset cannot be loaded, nullptr is returned. + + \param iname The filename of the image asset to find. + \returns The image asset, or nullptr if it cannot be loaded. + */ +Image_Asset* Image_Asset::find(const char *iname) { + if (!iname || !*iname) return nullptr; + + // First search to see if it exists already. If it does, return it. + auto result = image_asset_map.find(iname); + if (result != image_asset_map.end()) + return result->second; + + // Check if a file by that name exists. + Fluid.proj.enter_project_dir(); + FILE *f = fl_fopen(iname,"rb"); + if (!f) { + if (Fluid.batch_mode) + fprintf(stderr, "Can't open image file:\n%s\n%s",iname,strerror(errno)); + else + fl_message("Can't open image file:\n%s\n%s",iname,strerror(errno)); + Fluid.proj.leave_project_dir(); + return nullptr; + } + fclose(f); + + // We found the file. Create the asset. + Image_Asset *asset = new Image_Asset(iname); + if (!asset->image_ || !asset->image_->w() || !asset->image_->h()) { + delete asset; + if (Fluid.batch_mode) + fprintf(stderr, "Can't read image file:\n%s\nunrecognized image format",iname); + else + fl_message("Can't read image file:\n%s\nunrecognized image format",iname); + Fluid.proj.leave_project_dir(); + return nullptr; + } + + // Add the new asset to our image asset map and return it to the caller. + image_asset_map[iname] = asset; + return asset; +} + +/** + \brief Construct an image asset from a file in the project directory. + + This constructor creates an image asset from a file in the project + directory. The image file is loaded and stored in the map of image + assets. The image asset is given a reference count of 1, which means + that it will be destroyed when all references to it have been released. + The constructor also sets the flag to indicate if the image is an + animated GIF. This information is used when generating code for the + image asset. + + \param iname The name of the image file in the project directory. +*/ +Image_Asset::Image_Asset(const char *iname) +{ + filename_ = iname; + image_ = Fl_Shared_Image::get(iname); + if (image_ && iname) { + const char *ext = fl_filename_ext(iname); + if (fl_ascii_strcasecmp(ext, ".gif")==0) { + int fc = Fl_Anim_GIF_Image::frame_count(iname); + if (fc > 0) is_animated_gif_ = true; + } + } +} + +/** + \brief Increments the reference count of the image asset. + + This method increments the reference count of the image asset. The + reference count is used to keep track of how many times the image asset + is referenced in the user interface. When the reference count reaches zero, + the image asset is destroyed. +*/ +void Image_Asset::inc_ref() { + ++refcount_; +} + +/** + \brief Decrements the reference count of the image asset. + + This method decrements the reference count of the image asset. The + reference count is used to keep track of how many times the image asset + is referenced in the user interface. If the reference count reaches zero, + the image asset is destroyed. +*/ +void Image_Asset::dec_ref() { + --refcount_; + if (refcount_ > 0) return; + delete this; +} + +/** + \brief Destructor for the Image_Asset class. + + This destructor removes the image asset from the global image asset map + and releases the associated shared image if it exists. It ensures that + any resources associated with the Image_Asset are properly cleaned up + when the object is destroyed. +*/ +Image_Asset::~Image_Asset() { + image_asset_map.erase(filename_); + if (image_) image_->release(); +} + +//////////////////////////////////////////////////////////////// + +/** + \brief Displays a file chooser for the user to select an image file. + + This function displays a file chooser dialog with the title "Image?" and + the current working directory set to the project directory. The file + chooser displays files with the extensions .bm, .bmp, .gif, .jpg, .pbm, + .pgm, .png, .ppm, .xbm, .xpm, and .svg (and .svgz if zlib support is + enabled). The function returns a pointer to an Image_Asset object that + references the selected image. If the user cancels the file chooser or + selects a file that does not exist, the function returns nullptr. + + \param oldname The default filename to display in the file chooser. + + \return A pointer to an Image_Asset object that references the selected + image, or nullptr if the user cancels the file chooser or selects a file + that does not exist. The asset is automaticly added to the global image + asset map. +*/ +Image_Asset *ui_find_image(const char *oldname) { + Fluid.proj.enter_project_dir(); + fl_file_chooser_ok_label("Use Image"); + const char *name = fl_file_chooser("Image?", + "Image Files (*.{bm,bmp,gif,jpg,pbm,pgm,png,ppm,xbm,xpm,svg" +#ifdef HAVE_LIBZ + ",svgz" +#endif + "})", + oldname,1); + fl_file_chooser_ok_label(nullptr); + Image_Asset *ret = (name && *name) ? Image_Asset::find(name) : nullptr; + Fluid.proj.leave_project_dir(); + return ret; +} + |
