mirror of
				https://projects.vdr-developer.org/git/vdr-plugin-skindesigner.git
				synced 2023-10-19 15:58:31 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			534 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			534 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| #include "../config.h"
 | |
| #include "helpers.h"
 | |
| #include "imageloader.h"
 | |
| #include <string>
 | |
| #include <dirent.h>
 | |
| #include <iostream>
 | |
| #include <fstream>
 | |
| 
 | |
| cImageLoader::cImageLoader() {
 | |
|     importer = NULL;
 | |
| }
 | |
| 
 | |
| cImageLoader::~cImageLoader() {
 | |
|     delete(importer);
 | |
| }
 | |
| 
 | |
| cImage *cImageLoader::CreateImage(int width, int height, bool preserveAspect) {
 | |
|     if (!importer)
 | |
|         return NULL;
 | |
|     int w, h;
 | |
|     importer->GetImageSize(w, h);
 | |
|     if (width == 0)
 | |
|         width = w;
 | |
|     if (height == 0)
 | |
|         height = h;
 | |
| 
 | |
|     cairo_surface_t *surface;
 | |
|     surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
 | |
| 
 | |
|     cairo_t *cr;
 | |
|     cr = cairo_create(surface);
 | |
| 
 | |
|     double sx = width / (double)w;
 | |
|     double sy = height / (double)h;
 | |
|     if (preserveAspect) {
 | |
|         double tx = 0;
 | |
|         double ty = 0;
 | |
|         if (sx < sy) {
 | |
|             sy = sx;
 | |
|             ty = (height - h * sy) / 2;
 | |
|         }
 | |
|         if (sy < sx) {
 | |
|             sx = sy;
 | |
|             tx = (width - w * sx) / 2;
 | |
|         }
 | |
|         cairo_translate(cr, tx, ty);
 | |
|     }
 | |
|     cairo_scale(cr, sx, sy);
 | |
| 
 | |
|     importer->DrawToCairo(cr);
 | |
| 
 | |
|     cairo_status_t status = cairo_status(cr);
 | |
|     if (status && config.debugImageLoading)
 | |
|         dsyslog("skindesigner: Cairo CreateImage Error %s", cairo_status_to_string(status));
 | |
| 
 | |
|     unsigned char *data = cairo_image_surface_get_data(surface);
 | |
|     cImage *image = new cImage(cSize(width, height), (tColor*)data);
 | |
| 
 | |
|     cairo_destroy(cr);
 | |
|     cairo_surface_destroy(surface);
 | |
| 
 | |
|     return image;
 | |
| }
 | |
| 
 | |
| bool cImageLoader::LoadImage(const char *fullpath) {
 | |
|     if ((fullpath == NULL) || (strlen(fullpath) < 5))
 | |
|         return false;
 | |
| 
 | |
|     if (config.debugImageLoading)
 | |
|         dsyslog("skindesigner: trying to load: %s", fullpath);
 | |
| 
 | |
|     delete(importer);
 | |
|     importer = NULL;
 | |
| 
 | |
|     if (endswith(fullpath, ".png"))
 | |
|         importer = new cImageImporterPNG;
 | |
|     else if (endswith(fullpath, ".svg"))
 | |
|         importer = new cImageImporterSVG;
 | |
|     else if (endswith(fullpath, ".jpg"))
 | |
|         importer = new cImageImporterJPG;
 | |
|     else {
 | |
|         importer = cImageImporter::CreateImageImporter(fullpath);
 | |
|         if (!importer)
 | |
|             return false;
 | |
|     }
 | |
| 
 | |
|     return importer->LoadImage(fullpath);
 | |
| }
 | |
| 
 | |
| // Just a different way to call LoadImage. Calls the above one.
 | |
| bool cImageLoader::LoadImage(std::string Path, std::string FileName, std::string Extension) {
 | |
|     std::stringstream sstrImgFile;
 | |
|     sstrImgFile << Path << FileName << "." << Extension;
 | |
|     std::string imgFile = sstrImgFile.str();
 | |
|     return LoadImage(imgFile.c_str());
 | |
| }
 | |
| 
 | |
| cImageImporter* cImageImporter::CreateImageImporter(const char* path) {
 | |
|     char pngSig[] = { char(0x89), char(0x50), char(0x4E), char(0x47), char(0x0D), char(0x0A), char(0x1A), char(0x0A) };
 | |
|     char jpgSig[] = { char(0xFF), char(0xD8), char(0xFF), char(0xD9) };
 | |
|     char buffer[8] = { 0 };
 | |
|     ifstream f(path, ios::in | ios::binary);
 | |
|     f.read(buffer, 8);
 | |
|     if (!f)
 | |
|         return NULL;
 | |
|     if(buffer[0] == jpgSig[0] && buffer[1] == jpgSig[1]) {
 | |
|         f.seekg(-2, f.end);
 | |
|         f.read(buffer, 2);
 | |
|         if(buffer[0] == jpgSig[2] && buffer[1] == jpgSig[3]) {
 | |
|             f.close();
 | |
|             return new cImageImporterJPG;
 | |
|         }
 | |
|     } else if(buffer[0] == pngSig[0] 
 | |
|         && buffer[1] == pngSig[1]
 | |
|         && buffer[2] == pngSig[2]
 | |
|         && buffer[3] == pngSig[3]
 | |
|         && buffer[4] == pngSig[4]
 | |
|         && buffer[5] == pngSig[5]
 | |
|         && buffer[6] == pngSig[6]
 | |
|         && buffer[7] == pngSig[7]) {
 | |
|             f.close();
 | |
|             return new cImageImporterPNG;
 | |
|     } 
 | |
|     f.close();
 | |
|     return NULL;
 | |
| }
 | |
| 
 | |
| //
 | |
| // Image importer for PNG
 | |
| //
 | |
| 
 | |
| cImageImporterPNG::cImageImporterPNG() {
 | |
|     surface = NULL;
 | |
| }
 | |
| 
 | |
| cImageImporterPNG::~cImageImporterPNG() {
 | |
|     if (surface)
 | |
|         cairo_surface_destroy(surface);
 | |
| }
 | |
| 
 | |
| bool cImageImporterPNG::LoadImage(const char *path) {
 | |
|     if (surface)
 | |
|         cairo_surface_destroy(surface);
 | |
| 
 | |
|     surface = cairo_image_surface_create_from_png(path);
 | |
| 
 | |
|     if (cairo_surface_status(surface)) {
 | |
|         if (config.debugImageLoading)
 | |
|             dsyslog("skindesigner: Cairo LoadImage Error: %s", cairo_status_to_string(cairo_surface_status(surface)));
 | |
|         surface = NULL;
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| void cImageImporterPNG::DrawToCairo(cairo_t *cr) {
 | |
|     if (surface) {
 | |
|         cairo_set_source_surface(cr, surface, 0, 0);
 | |
|         cairo_paint(cr);
 | |
|     }
 | |
| }
 | |
| 
 | |
| void cImageImporterPNG::GetImageSize(int &width, int &height) {
 | |
|     if (surface) {
 | |
|         width = cairo_image_surface_get_width(surface);
 | |
|         height = cairo_image_surface_get_height(surface);
 | |
|     }
 | |
| }
 | |
| 
 | |
| //
 | |
| // Image importer for SVG
 | |
| //
 | |
| 
 | |
| cImageImporterSVG::cImageImporterSVG() {
 | |
|     handle = NULL;
 | |
| }
 | |
| 
 | |
| cImageImporterSVG::~cImageImporterSVG() {
 | |
|     if (handle) {
 | |
| #if LIBRSVG_CHECK_VERSION (2, 46, 0)
 | |
|         // rsvg_handle_close is deprecated since version 2.46 and looks like even not required if used with rsvg_handle_new_from_file
 | |
| #else
 | |
|         rsvg_handle_close(handle, NULL);
 | |
| #endif
 | |
|         g_object_unref(handle);
 | |
|     }
 | |
| }
 | |
| 
 | |
| bool cImageImporterSVG::LoadImage(const char *path) {
 | |
|     if (handle) {
 | |
| #if LIBRSVG_CHECK_VERSION (2, 46, 0)
 | |
|         // rsvg_handle_close is deprecated since version 2.46 and looks like even not required if used with rsvg_handle_new_from_file
 | |
| #else
 | |
|         rsvg_handle_close(handle, NULL);
 | |
| #endif
 | |
|         g_object_unref(handle);
 | |
|     }
 | |
| 
 | |
|     GError *error = NULL;
 | |
|     handle = rsvg_handle_new_from_file(path, &error);
 | |
|     if (!handle) {
 | |
|         if (config.debugImageLoading && error) {
 | |
|             dsyslog("skindesigner: RSVG Error: %s", error->message);
 | |
|         }
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     // 90 dpi is the hardcoded default setting of the Inkscape SVG editor
 | |
|     rsvg_handle_set_dpi(handle, 90);
 | |
| 
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| void cImageImporterSVG::DrawToCairo(cairo_t *cr) {
 | |
|     if (handle)
 | |
|         rsvg_handle_render_cairo(handle, cr);
 | |
| }
 | |
| 
 | |
| void cImageImporterSVG::GetImageSize(int &width, int &height) {
 | |
|     if (handle) {
 | |
|         RsvgDimensionData dim;
 | |
|         rsvg_handle_get_dimensions(handle, &dim);
 | |
|         width = dim.width;
 | |
|         height = dim.height;
 | |
|     }
 | |
| }
 | |
| 
 | |
| void cImageImporterSVG::InitLibRSVG() {
 | |
|     #if !GLIB_CHECK_VERSION(2, 35, 0)
 | |
|         g_type_init();
 | |
|     #endif
 | |
| }
 | |
| 
 | |
| //
 | |
| // Image importer for JPG
 | |
| //
 | |
| 
 | |
| struct my_error_mgr {
 | |
|     struct jpeg_error_mgr pub; // "public" fields
 | |
|     jmp_buf setjmp_buffer;     // for return to caller
 | |
| };
 | |
| 
 | |
| METHODDEF(void)
 | |
| my_error_exit(j_common_ptr cinfo) {
 | |
|     // cinfo->err really points to a my_error_mgr struct, so coerce pointer
 | |
|     my_error_mgr *myerr = (my_error_mgr*) cinfo->err;
 | |
| 
 | |
|     // Always display the message.
 | |
|     (*cinfo->err->output_message) (cinfo);
 | |
| 
 | |
|     // Return control to the setjmp point
 | |
|     longjmp(myerr->setjmp_buffer, 1);
 | |
| }
 | |
| 
 | |
| METHODDEF(void)
 | |
| my_output_message(j_common_ptr cinfo) {
 | |
|     char buf[JMSG_LENGTH_MAX];
 | |
|     cinfo->err->format_message(cinfo, buf);
 | |
|     if (config.debugImageLoading)
 | |
|         dsyslog("skindesigner: libjpeg error: %s", buf);
 | |
| }
 | |
| 
 | |
| cImageImporterJPG::cImageImporterJPG() {
 | |
|     cinfo = NULL;
 | |
| }
 | |
| 
 | |
| cImageImporterJPG::~cImageImporterJPG() {
 | |
|     if (cinfo) {
 | |
|         jpeg_destroy_decompress(cinfo);
 | |
|         free(cinfo);
 | |
|         fclose(infile);
 | |
|     }
 | |
| }
 | |
| 
 | |
| bool cImageImporterJPG::LoadImage(const char *path) {
 | |
|     if (cinfo) {
 | |
|         jpeg_destroy_decompress(cinfo);
 | |
|         free(cinfo);
 | |
|         fclose(infile);
 | |
|         cinfo = NULL;
 | |
|     }
 | |
| 
 | |
|     // Open input file
 | |
|     if ((infile = fopen(path, "rb")) == NULL) {
 | |
|         if (config.debugImageLoading)
 | |
|             dsyslog("skindesigner: Can't open %s", path);
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     // Allocate space for our decompress struct
 | |
|     cinfo = (j_decompress_ptr)malloc(sizeof(struct jpeg_decompress_struct));
 | |
| 
 | |
|     // We set up the normal JPEG error routines, then override error_exit
 | |
|     // and output_message.
 | |
|     struct my_error_mgr jerr;
 | |
|     cinfo->err = jpeg_std_error(&jerr.pub);
 | |
|     jerr.pub.error_exit = my_error_exit;
 | |
|     jerr.pub.output_message = my_output_message;
 | |
|     // Establish the setjmp return context for my_error_exit to use.
 | |
|     if (setjmp(jerr.setjmp_buffer)) {
 | |
|         // If we get here, the JPEG code has signaled an error.
 | |
|         jpeg_destroy_decompress(cinfo);
 | |
|         free(cinfo);
 | |
|         fclose(infile);
 | |
|         cinfo = NULL;
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     // Now we can initialize the JPEG decompression object.
 | |
|     jpeg_create_decompress(cinfo);
 | |
| 
 | |
|     // Step 2: specify data source (eg, a file)
 | |
|     jpeg_stdio_src(cinfo, infile);
 | |
| 
 | |
|     // Step 3: read file parameters with jpeg_read_header()
 | |
|     (void) jpeg_read_header(cinfo, TRUE);
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| void cImageImporterJPG::DrawToCairo(cairo_t *cr) {
 | |
|     if (!cinfo)
 | |
|         return;
 | |
| 
 | |
|     unsigned char *bmp_buffer = NULL;
 | |
| 
 | |
|     // Re-establish error handling. We have to do this again as the saved
 | |
|     // calling environment of "LoadImage" is invalid if we reach here!
 | |
|     struct my_error_mgr jerr;
 | |
|     cinfo->err = jpeg_std_error(&jerr.pub);
 | |
|     jerr.pub.error_exit = my_error_exit;
 | |
|     jerr.pub.output_message = my_output_message;
 | |
|     if (setjmp(jerr.setjmp_buffer)) {
 | |
|         jpeg_destroy_decompress(cinfo);
 | |
|         free(cinfo);
 | |
|         fclose(infile);
 | |
|         free(bmp_buffer);
 | |
|         cinfo = NULL;
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     // Step 4: set parameters for decompression
 | |
|     cinfo->out_color_space = JCS_RGB;
 | |
| 
 | |
|     // Step 5: Start decompressor
 | |
|     (void) jpeg_start_decompress(cinfo);
 | |
| 
 | |
|     // Allocate buffer. Directly allocate the space needed for ARGB
 | |
|     unsigned int width = cinfo->output_width;
 | |
|     unsigned int height = cinfo->output_height;
 | |
|     bmp_buffer = (unsigned char*)malloc(width * height * 4);
 | |
| 
 | |
|     // Step 6: while (scan lines remain to be read)
 | |
|     int jpg_stride = width * cinfo->output_components;
 | |
|     while (cinfo->output_scanline < height) {
 | |
|         unsigned char *buffer_array[1];
 | |
|         buffer_array[0] = bmp_buffer + (cinfo->output_scanline) * jpg_stride;
 | |
|         jpeg_read_scanlines(cinfo, buffer_array, 1);
 | |
|     }
 | |
| 
 | |
|     // Step 7: Finish decompression.
 | |
|     (void)jpeg_finish_decompress(cinfo);
 | |
| 
 | |
|     // Cleanup. In this "ImageImporter" we clean up everything in "DrawToCairo"
 | |
|     // as I'm not really sure whether we are able to draw a second time.
 | |
|     fclose(infile);
 | |
|     jpeg_destroy_decompress(cinfo);
 | |
|     free(cinfo);
 | |
|     cinfo = NULL;
 | |
| 
 | |
|     // --> At this point we have raw RGB data in bmp_buffer
 | |
| 
 | |
|     // Do some ugly byte shifting.
 | |
|     // Byte order in libjpeg: RGB
 | |
|     // Byte order in cairo and VDR: BGRA
 | |
|     unsigned char temp[3];
 | |
|     for (int index = (width * height) - 1; index >= 0; index--) {
 | |
|         unsigned char *target = bmp_buffer + (index * 4);
 | |
|         unsigned char *source = bmp_buffer + (index * 3);
 | |
|         memcpy(&temp[0], source + 2, 1);
 | |
|         memcpy(&temp[1], source + 1, 1);
 | |
|         memcpy(&temp[2], source, 1);
 | |
|         memcpy(target, &temp, 3);
 | |
|     }
 | |
| 
 | |
|     // Create new Cairo surface from our raw image data
 | |
|     cairo_surface_t *surface;
 | |
|     surface = cairo_image_surface_create_for_data(bmp_buffer,
 | |
|                                                   CAIRO_FORMAT_RGB24,
 | |
|                                                   width,
 | |
|                                                   height,
 | |
|                                                   width * 4);
 | |
| 
 | |
|     // Draw surface to Cairo
 | |
|     if (surface) {
 | |
|         cairo_set_source_surface(cr, surface, 0, 0);
 | |
|         cairo_paint(cr);
 | |
|         cairo_surface_destroy(surface);
 | |
|     }
 | |
| 
 | |
|     // Free our memory
 | |
|     free(bmp_buffer);
 | |
| }
 | |
| 
 | |
| void cImageImporterJPG::GetImageSize(int &width, int &height) {
 | |
|     if (cinfo) {
 | |
|         width = cinfo->image_width;
 | |
|         height = cinfo->image_height;
 | |
|     }
 | |
| }
 | |
| 
 | |
| //
 | |
| // SVG Template class
 | |
| //
 | |
| 
 | |
| cSVGTemplate::cSVGTemplate(string imageName, string imagePath, string templatePath) {
 | |
|     this->imageName = imageName;
 | |
|     this->imagePath = imagePath;
 | |
|     this->templatePath = templatePath;
 | |
|     filePath = CreateImagePath();
 | |
|     startTokenColor = "{sdcol(";
 | |
|     startTokenOpac = "{sdopac(";
 | |
|     endToken = ")}";
 | |
| }
 | |
| 
 | |
| cSVGTemplate::~cSVGTemplate(void) {
 | |
| }
 | |
| 
 | |
| string cSVGTemplate::CreateImagePath(void) {
 | |
|     //check if imageName is a path
 | |
|     if (imageName.find("/") != string::npos) {
 | |
|         splitstring s(imageName.c_str());
 | |
|         vector<string> flds = s.split('/', 1);
 | |
|         int num = flds.size() - 1;
 | |
|         for (int i=0; i < num; i++) {
 | |
|             imagePath = *cString::sprintf("%s/%s", imagePath.c_str(), flds[i].c_str());
 | |
|         }
 | |
|         imageName = flds[num];
 | |
|     }
 | |
|     string path = templatePath;
 | |
|     path += imagePath + "/" + imageName + ".svg";
 | |
|     return path;
 | |
| }
 | |
| 
 | |
| bool cSVGTemplate::Exists(void) {
 | |
|     return FileExists(filePath);
 | |
| }
 | |
| 
 | |
| void cSVGTemplate::ReadTemplate(void) {
 | |
|     string line;
 | |
|     ifstream templatefile(filePath.c_str());
 | |
|     if (templatefile.is_open()) {
 | |
|         while ( getline (templatefile, line) ) {
 | |
|             svgTemplate.push_back(line);
 | |
|         }
 | |
|         templatefile.close();
 | |
|     }
 | |
| }
 | |
| 
 | |
| bool cSVGTemplate::ParseTemplate(void) {
 | |
|     int i = 0;
 | |
|     for (vector<string>::iterator it = svgTemplate.begin(); it != svgTemplate.end(); it++) {
 | |
|         string line = *it;
 | |
|         size_t hit = line.find(startTokenColor);
 | |
|         if (hit == string::npos) {
 | |
|             i++;
 | |
|             continue;
 | |
|         }
 | |
|         while (hit != string::npos) {
 | |
|             size_t hitEnd = line.find(endToken, hit);
 | |
|             if (hitEnd == string::npos) {
 | |
|                 esyslog("skindesigner: error in SVG Template %s: invalid tag", imageName.c_str());
 | |
|                 return false;
 | |
|             }
 | |
|             string colorName = GetColorName(line, hit, hitEnd);
 | |
|             tColor replaceColor = 0x0;
 | |
|             if (!config.GetThemeColor(colorName, replaceColor)) {
 | |
|                 esyslog("skindesigner: error in SVG Template %s: invalid color %x", imageName.c_str(), replaceColor);
 | |
|                 return false;
 | |
|             }
 | |
|             ReplaceTokens(line, hit, hitEnd, replaceColor);
 | |
|             hit = line.find(startTokenColor);
 | |
|         }        
 | |
|         svgTemplate[i] = line;
 | |
|         i++;
 | |
|     }
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| string cSVGTemplate::WriteImage(void) {
 | |
|     string tempPath = *cString::sprintf("/tmp/skindesigner/svg/%s/%s/%s/", Setup.OSDSkin, Setup.OSDTheme, imagePath.c_str());
 | |
|     CreateFolder(tempPath);
 | |
|     string fileName = tempPath + imageName + ".svg";
 | |
|     ofstream tmpimg;
 | |
|     tmpimg.open (fileName.c_str(), ios::out | ios::trunc);
 | |
|     if (!tmpimg.is_open()) {
 | |
|         return "";
 | |
|     }
 | |
|     for (vector<string>::iterator it = svgTemplate.begin(); it != svgTemplate.end(); it++) {
 | |
|         tmpimg << (*it) << "\n";
 | |
|     }
 | |
|     tmpimg.close();
 | |
|     return fileName;
 | |
| }
 | |
| 
 | |
| string cSVGTemplate::GetColorName(string line, size_t tokenStart, size_t tokenEnd) {
 | |
|     string colorName = line.substr(tokenStart + startTokenColor.size(), tokenEnd - tokenStart - startTokenColor.size());
 | |
|     if (colorName.size() > 0) {
 | |
|         stringstream name;
 | |
|         name << "{" << colorName << "}";
 | |
|         return name.str();
 | |
|     }
 | |
|     return "";
 | |
| }
 | |
| 
 | |
| void cSVGTemplate::ReplaceTokens(string &line, size_t tokenStart, size_t tokenEnd, tColor color) {
 | |
|     string rgbColor = *cString::sprintf("%06x", color & 0x00FFFFFF);
 | |
|     line.replace(tokenStart, tokenEnd - tokenStart + 2, rgbColor);
 | |
|     size_t hitAlpha = line.find(startTokenOpac);
 | |
|     if (hitAlpha == string::npos) {
 | |
|         return;
 | |
|     }
 | |
|     size_t hitAlphaEnd = line.find(endToken, hitAlpha);
 | |
|     if (hitAlphaEnd == string::npos) {
 | |
|         return;
 | |
|     }
 | |
|     tIndex alpha = (color & 0xFF000000) >> 24;
 | |
|     string svgAlpha = *cString::sprintf("%f", (float)(alpha / (float)255));
 | |
|     std::replace( svgAlpha.begin(), svgAlpha.end(), ',', '.');
 | |
|     line.replace(hitAlpha, hitAlphaEnd - hitAlpha + 2, svgAlpha);
 | |
| }
 | |
| 
 | |
| // vim: ts=4 sw=4 et
 |