LCOV - code coverage report
Current view: top level - core/bitmap - SPBitmapJpeg.cc (source / functions) Hit Total Coverage
Test: coverage.info Lines: 156 177 88.1 %
Date: 2024-05-12 00:16:13 Functions: 16 17 94.1 %

          Line data    Source code
       1             : /**
       2             : Copyright (c) 2022 Roman Katuntsev <sbkarr@stappler.org>
       3             : Copyright (c) 2023 Stappler LLC <admin@stappler.dev>
       4             : 
       5             : Permission is hereby granted, free of charge, to any person obtaining a copy
       6             : of this software and associated documentation files (the "Software"), to deal
       7             : in the Software without restriction, including without limitation the rights
       8             : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       9             : copies of the Software, and to permit persons to whom the Software is
      10             : furnished to do so, subject to the following conditions:
      11             : 
      12             : The above copyright notice and this permission notice shall be included in
      13             : all copies or substantial portions of the Software.
      14             : 
      15             : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
      16             : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      17             : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
      18             : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      19             : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
      20             : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
      21             : THE SOFTWARE.
      22             : **/
      23             : 
      24             : #include "SPBitmapFormat.h"
      25             : #include "SPLog.h"
      26             : #include "SPFilesystem.h"
      27             : #include "jpeglib.h"
      28             : #include <setjmp.h>
      29             : 
      30             : namespace STAPPLER_VERSIONIZED stappler::bitmap::jpeg {
      31             : 
      32             : struct JpegError {
      33             :     struct jpeg_error_mgr pub;  /* "public" fields */
      34             :     jmp_buf setjmp_buffer;      /* for return to caller */
      35             : 
      36           0 :     static void ErrorExit(j_common_ptr cinfo) {
      37           0 :         JpegError * myerr = (JpegError *) cinfo->err;
      38             :             char buffer[JMSG_LENGTH_MAX];
      39           0 :             (*cinfo->err->format_message) (cinfo, buffer);
      40           0 :             log::error("JPEG", "jpeg error: %s", buffer);
      41           0 :             longjmp(myerr->setjmp_buffer, 1);
      42             :         }
      43             : };
      44             : 
      45        5250 : static bool isJpg(const uint8_t * data, size_t dataLen) {
      46        5250 :     if (dataLen <= 4) {
      47           0 :         return false;
      48             :     }
      49             : 
      50             :     static const unsigned char JPG_SOI[] = {0xFF, 0xD8};
      51        5250 :     return memcmp(data, JPG_SOI, 2) == 0;
      52             : }
      53             : 
      54        1175 : static bool getJpegImageSize(const io::Producer &file, StackBuffer<512> &data, uint32_t &width, uint32_t &height) {
      55        1175 :         if (isJpg(data.data(), data.size())) {
      56         225 :                 size_t offset = 2;
      57         225 :                 uint16_t len = 0;
      58         225 :                 uint8_t marker = 0;
      59             : 
      60         225 :                 auto reader = BytesViewNetwork(data.data() + 2, data.size() - 2);
      61         450 :                 while (reader.is((uint8_t) 0xFF)) {
      62         225 :                         ++reader;
      63         225 :                         ++offset;
      64             :                 }
      65             : 
      66         225 :                 marker = reader.readUnsigned();
      67         225 :                 len = reader.readUnsigned16();
      68             : 
      69        2325 :                 while (marker < 0xC0 || marker > 0xCF || marker == 0xC4) {
      70        2100 :                         offset += 1 + len;
      71        2100 :                         data.clear();
      72             : 
      73        2100 :                         if (file.seekAndRead(offset, data, 12) != 12) {
      74           0 :                                 return false;
      75             :                         }
      76             : 
      77        2100 :                         if (data.size() >= 12) {
      78        2100 :                                 reader = data.get<BytesViewNetwork>();
      79             : 
      80        4200 :                                 while (reader.is((uint8_t) 0xFF)) {
      81        2100 :                                         ++reader;
      82        2100 :                                         ++offset;
      83             :                                 }
      84             : 
      85        2100 :                                 marker = reader.readUnsigned();
      86        2100 :                                 len = reader.readUnsigned16();
      87             :                         } else {
      88           0 :                                 reader.clear();
      89           0 :                                 break;
      90             :                         }
      91             :                 }
      92             : 
      93         225 :                 if (reader >= 5 && marker >= 0xC0 && marker <= 0xCF && marker != 0xC4) {
      94         225 :                         ++reader;
      95         225 :                         height = reader.readUnsigned16();
      96         225 :                         width = reader.readUnsigned16();
      97         225 :                         return true;
      98             :                 }
      99             : 
     100           0 :                 return false;
     101             :         }
     102         950 :         return false;
     103             : }
     104             : 
     105             : struct JpegReadStruct {
     106         175 :         ~JpegReadStruct() {
     107         175 :                 if (initialized) {
     108         175 :                         jpeg_destroy_decompress(&cinfo);
     109         175 :                         initialized = false;
     110             :                 }
     111             : 
     112         175 :         }
     113             : 
     114         175 :         JpegReadStruct() {
     115         175 :                 cinfo.err = jpeg_std_error(&jerr.pub);
     116         175 :                 jerr.pub.error_exit = &JpegError::ErrorExit;
     117         175 :         }
     118             : 
     119         175 :         bool init(const uint8_t *inputData, size_t size) {
     120         175 :                 if (setjmp(jerr.setjmp_buffer)) {
     121           0 :                         return false;
     122             :                 }
     123             : 
     124         175 :                 jpeg_create_decompress( &cinfo );
     125         175 :                 initialized = true;
     126         175 :                 jpeg_mem_src(&cinfo, const_cast<unsigned char*>(inputData), size);
     127             : 
     128             :                 /* reading the image header which contains image information */
     129         175 :                 jpeg_read_header(&cinfo, boolean(TRUE));
     130             : 
     131         175 :                 return true;
     132             :         }
     133             : 
     134         175 :         bool info(ImageInfo &info) {
     135         175 :                 if (setjmp(jerr.setjmp_buffer)) {
     136           0 :                         return false;
     137             :                 }
     138             : 
     139             :                 // we only support RGB or grayscale
     140         175 :                 if (cinfo.jpeg_color_space == JCS_GRAYSCALE) {
     141          50 :                         info.color = (info.color == PixelFormat::A8 ? PixelFormat::A8 : PixelFormat::I8);
     142         125 :                 } else if (cinfo.jpeg_color_space == JCS_YCCK || cinfo.jpeg_color_space == JCS_CMYK) {
     143          50 :                         cinfo.out_color_space = JCS_CMYK;
     144          50 :                         info.color = PixelFormat::RGB888;
     145             :                 } else {
     146          75 :                         cinfo.out_color_space = JCS_RGB;
     147          75 :                         info.color = PixelFormat::RGB888;
     148             :                 }
     149             : 
     150         175 :                 if (info.color == PixelFormat::I8 || info.color == PixelFormat::RGB888) {
     151         175 :                         info.alpha = AlphaFormat::Opaque;
     152             :                 } else {
     153           0 :                         info.alpha = AlphaFormat::Unpremultiplied;
     154             :                 }
     155             : 
     156         175 :                 jpeg_calc_output_dimensions( &cinfo );
     157             : 
     158         175 :                 info.width  = cinfo.output_width;
     159         175 :                 info.height = cinfo.output_height;
     160         175 :                 info.stride = cinfo.output_width * getBytesPerPixel(info.color);
     161             : 
     162         175 :                 return true;
     163             :         }
     164             : 
     165         100 :         bool load(BitmapWriter &outputData) {
     166         100 :                 if (!info(outputData)) {
     167           0 :                         return false;
     168             :                 }
     169             : 
     170         100 :                 if (setjmp(jerr.setjmp_buffer)) {
     171           0 :                         return false;
     172             :                 }
     173             : 
     174         100 :                 if (outputData.getStride) {
     175          75 :                         outputData.stride = max(outputData.getStride(outputData.target, outputData.color, outputData.width),
     176         150 :                                         uint32_t(cinfo.output_width * getBytesPerPixel(outputData.color)));
     177             :                 }
     178             : 
     179             :                 /* Start decompression jpeg here */
     180         100 :                 jpeg_start_decompress( &cinfo );
     181             : 
     182             : 
     183         100 :                 auto dataLen = outputData.height * outputData.stride;
     184         100 :                 outputData.resize(outputData.target, dataLen);
     185             : 
     186         100 :                 JSAMPROW row_pointer[1] = {0};
     187         100 :                 unsigned long location = 0;
     188             : 
     189         100 :                 if (cinfo.out_color_space == JCS_CMYK || cinfo.out_color_space == JCS_YCCK) {
     190          25 :                         memory::PoolInterface::BytesType buf; buf.resize(cinfo.output_width * cinfo.output_components);
     191        8350 :                         while (cinfo.output_scanline < cinfo.output_height) {
     192        8325 :                                 row_pointer[0] = buf.data();
     193        8325 :                                 jpeg_read_scanlines(&cinfo, row_pointer, 1);
     194             : 
     195        8325 :                                 auto loc = outputData.getData(outputData.target, location);
     196     4170825 :                                 for (size_t i = 0; i < cinfo.output_width; ++ i) {
     197     4162500 :                                         *loc++ = (buf[i * 4]) * (buf[i * 4 + 3]) / 255;
     198     4162500 :                                         *loc++ = (buf[i * 4 + 1]) * (buf[i * 4 + 3]) / 255;
     199     4162500 :                                         *loc++ = (buf[i * 4 + 2]) * (buf[i * 4 + 3]) / 255;
     200             :                                 }
     201        8325 :                                 location += outputData.stride;
     202             :                         }
     203          25 :                 } else {
     204             :                         /* now actually read the jpeg into the raw buffer */
     205             :                         /* read one scan line at a time */
     206       17525 :                         while (cinfo.output_scanline < cinfo.output_height) {
     207       17450 :                                 row_pointer[0] =  outputData.getData(outputData.target, location);
     208       17450 :                                 location += outputData.stride;
     209       17450 :                                 jpeg_read_scanlines(&cinfo, row_pointer, 1);
     210             :                         }
     211             :                 }
     212             : 
     213         100 :                 return true;
     214             :         }
     215             : 
     216             :         bool initialized = false;
     217             :         struct jpeg_decompress_struct cinfo;
     218             :         struct JpegError jerr;
     219             : };
     220             : 
     221             : struct JpegWriteStruct {
     222             :         struct jpeg_compress_struct cinfo;
     223             :         struct jpeg_error_mgr jerr;
     224             :         bool valid = false;
     225             : 
     226             :         FILE *fp = nullptr;
     227             :         BitmapWriter *vec = nullptr;
     228             : 
     229             :         unsigned char *mem = nullptr;
     230             :         size_t memSize = 0;
     231             : 
     232         250 :         ~JpegWriteStruct() {
     233         250 :                 jpeg_destroy_compress( &cinfo );
     234             : 
     235         250 :                 if (fp) {
     236         175 :                         fclose(fp);
     237             :                 }
     238         250 :         if (mem) {
     239          75 :                 free(mem);
     240             :         }
     241         250 :         }
     242             : 
     243         250 :         JpegWriteStruct() {
     244         250 :                 cinfo.err = jpeg_std_error( &jerr );
     245         250 :                 jpeg_create_compress(&cinfo);
     246         250 :         }
     247             : 
     248          75 :         JpegWriteStruct(BitmapWriter *v) : JpegWriteStruct() {
     249          75 :                 vec = v;
     250          75 :                 jpeg_mem_dest(&cinfo, &mem, &memSize);
     251          75 :                 valid = true;
     252          75 :         }
     253             : 
     254         175 :         JpegWriteStruct(const StringView &filename) : JpegWriteStruct() {
     255         175 :                 fp = filesystem::native::fopen_fn(filename, "wb");
     256         175 :                 if (!fp) {
     257           0 :                         log::format(log::Error, "Bitmap", "fail to open file '%s' to write jpeg data", filename.data());
     258           0 :                         valid = false;
     259           0 :                         return;
     260             :                 }
     261             : 
     262         175 :                 jpeg_stdio_dest(&cinfo, fp);
     263         175 :                 valid = true;
     264           0 :         }
     265             : 
     266         250 :         bool write(const uint8_t *data, BitmapWriter &state, bool invert = false) {
     267         250 :                 if (!valid) {
     268           0 :                         return false;
     269             :                 }
     270             : 
     271             :                 /* this is a pointer to one row of image data */
     272             :                 JSAMPROW row_pointer[1];
     273             : 
     274             :                 /* Setting the parameters of the output file here */
     275         250 :                 cinfo.image_width = state.width;
     276         250 :                 cinfo.image_height = state.height;
     277         250 :                 cinfo.input_components = getBytesPerPixel(state.color);
     278             : 
     279         250 :             switch (state.color) {
     280          25 :             case PixelFormat::A8:
     281             :             case PixelFormat::I8:
     282          25 :                 cinfo.input_components = 1;
     283          25 :                 cinfo.in_color_space = JCS_GRAYSCALE;
     284          25 :                 break;
     285         175 :             case PixelFormat::RGB888:
     286         175 :                 cinfo.input_components = 3;
     287         175 :                 cinfo.in_color_space = JCS_RGB;
     288         175 :                 break;
     289          50 :             default:
     290          50 :                         log::error("JPEG", "Color format is not supported by JPEG!");
     291          50 :                 return false;
     292             :                 break;
     293             :             }
     294             : 
     295             :             /* default compression parameters, we shouldn't be worried about these */
     296         200 :             jpeg_set_defaults( &cinfo );
     297         200 :             jpeg_set_quality( &cinfo, 90, boolean(TRUE) );
     298             :             /* Now do the compression .. */
     299         200 :             jpeg_start_compress( &cinfo, boolean(TRUE) );
     300             :             /* like reading a file, this time write one row at a time */
     301       26450 :             while( cinfo.next_scanline < cinfo.image_height ) {
     302       26250 :                 row_pointer[0] = (JSAMPROW) &data[ (invert ? (state.height - 1 - cinfo.next_scanline) : cinfo.next_scanline) * state.stride ];
     303       26250 :                 jpeg_write_scanlines( &cinfo, row_pointer, 1 );
     304             :             }
     305             :             /* similar to read file, clean up after we're done compressing */
     306         200 :             jpeg_finish_compress( &cinfo );
     307             : 
     308         200 :             if (vec) {
     309          25 :                 if (memSize) {
     310          25 :                         vec->assign(vec->target, mem, memSize);
     311             :                 } else {
     312           0 :                         return false;
     313             :                 }
     314             :             }
     315             : 
     316         200 :             return true;
     317             :         }
     318             : };
     319             : 
     320          75 : static bool infoJpg(const uint8_t *inputData, size_t size, ImageInfo &outputData) {
     321          75 :         JpegReadStruct jpegStruct;
     322         150 :         return jpegStruct.init(inputData, size) && jpegStruct.info(outputData);
     323          75 : }
     324             : 
     325         100 : static bool loadJpg(const uint8_t *inputData, size_t size, BitmapWriter &outputData) {
     326         100 :         JpegReadStruct jpegStruct;
     327         200 :         return jpegStruct.init(inputData, size) && jpegStruct.load(outputData);
     328         100 : }
     329             : 
     330         175 : static bool saveJpeg(StringView filename, const uint8_t *data, BitmapWriter &state, bool invert) {
     331         175 :         JpegWriteStruct s(filename);
     332         350 :         return s.write(data, state, invert);
     333         175 : }
     334             : 
     335          75 : static bool writeJpeg(const uint8_t *data, BitmapWriter &state, bool invert) {
     336          75 :         JpegWriteStruct s(&state);
     337         150 :         return s.write(data, state, invert);
     338          75 : }
     339             : 
     340             : }

Generated by: LCOV version 1.14