1 /** 2 JPEG support. 3 4 Copyright: Copyright Guillaume Piolat 2022 5 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 */ 7 module gamut.plugins.jpeg; 8 9 nothrow @nogc @safe: 10 11 import core.stdc.stdlib: malloc, free, realloc; 12 import gamut.types; 13 import gamut.image; 14 import gamut.io; 15 import gamut.plugin; 16 import gamut.codecs.jpegload; 17 import gamut.codecs.stb_image_write; 18 import gamut.internals.errors; 19 import gamut.internals.types; 20 21 22 ImageFormatPlugin makeJPEGPlugin() 23 { 24 ImageFormatPlugin p; 25 p.format = "JPEG"; 26 p.extensionList = "jpg,jpeg,jif,jfif"; 27 p.mimeTypes = "image/jpeg"; 28 version(decodeJPEG) 29 p.loadProc = &loadJPEG; 30 else 31 p.loadProc = null; 32 version(encodeJPEG) 33 p.saveProc = &saveJPEG; 34 else 35 p.saveProc = null; 36 p.detectProc = &detectJPEG; 37 return p; 38 } 39 40 41 version(decodeJPEG) 42 void loadJPEG(ref Image image, IOStream *io, IOHandle handle, int page, int flags, void *data) @trusted 43 { 44 JPEGIOHandle jio; 45 jio.wrapped = io; 46 jio.handle = handle; 47 48 int requestedComp = computeRequestedImageComponents(flags); 49 if (requestedComp == 0) 50 { 51 image.error(kStrInvalidFlags); 52 return; 53 } 54 55 if (requestedComp == 2) // JPEG reader doesn't convert to greyscale+alpha on the fly 56 requestedComp = -1; 57 58 int width, height, actualComp; 59 float pixelAspectRatio; 60 float dotsPerInchY; 61 ubyte[] decoded = decompress_jpeg_image_from_stream(&stream_read_jpeg, &jio, width, height, actualComp, pixelAspectRatio, dotsPerInchY, requestedComp); 62 if (decoded is null) 63 { 64 image.error(kStrImageDecodingFailed); 65 return; 66 } 67 68 if (actualComp != 1 && actualComp != 3 && actualComp != 4) 69 { 70 image.error(kStrImageWrongComponents); 71 free(decoded.ptr); 72 return; 73 } 74 75 if (!imageIsValidSize(width, height)) 76 { 77 image.error(kStrImageTooLarge); 78 free(decoded.ptr); 79 return; 80 } 81 82 image._width = width; 83 image._height = height; 84 image._allocArea = decoded.ptr; 85 image._data = decoded.ptr; 86 image._pitch = width * actualComp; 87 image._pixelAspectRatio = pixelAspectRatio == -1 ? GAMUT_UNKNOWN_ASPECT_RATIO : pixelAspectRatio; 88 image._resolutionY = dotsPerInchY == -1 ? GAMUT_UNKNOWN_RESOLUTION : dotsPerInchY; 89 image._layoutConstraints = LAYOUT_DEFAULT; // JPEG decoder follow no particular constraints (TODO?) 90 91 switch (actualComp) 92 { 93 case 1: image._type = PixelType.l8; break; 94 case 3: image._type = PixelType.rgb8; break; 95 case 4: image._type = PixelType.rgba8; break; 96 default: 97 } 98 99 // Convert to target type and constraints 100 image.convertTo(applyLoadFlags(image._type, flags), cast(LayoutConstraints) flags); 101 } 102 103 bool detectJPEG(IOStream *io, IOHandle handle) @trusted 104 { 105 static immutable ubyte[2] jpegSignature = [0xFF, 0xD8]; 106 return fileIsStartingWithSignature(io, handle, jpegSignature); 107 } 108 109 version(encodeJPEG) 110 bool saveJPEG(ref const(Image) image, IOStream *io, IOHandle handle, int page, int flags, void *data) @trusted 111 { 112 if (page != 0) 113 return false; 114 115 int components; 116 117 switch (image._type) 118 { 119 case PixelType.l8: 120 components = 1; break; 121 case PixelType.rgb8: 122 components = 3; 123 break; 124 case PixelType.rgba8: 125 return false; // stb would throw away alpha 126 default: 127 return false; 128 } 129 130 JPEGIOHandle jio; 131 jio.wrapped = io; 132 jio.handle = handle; 133 134 void* userPointer = cast(void*)&jio; 135 136 int quality = 90; // TODO: option to choose that. 137 138 int res = stbi_write_jpg_to_func(&stb_stream_write, userPointer, 139 image._width, 140 image._height, 141 components, 142 image._data, quality); 143 144 return res == 1 && !jio.errored; 145 } 146 147 private: 148 149 150 struct JPEGIOHandle 151 { 152 IOStream* wrapped; 153 IOHandle handle; 154 155 // stb_image_write doesn't check errors for write, so keep a flag and start ignoring output if 156 // an I/O error occurs. 157 bool errored = false; 158 } 159 160 /// This function is called when the internal input buffer is empty. 161 // userData must be a JPEGIOHandle* 162 int stream_read_jpeg(void* pBuf, int max_bytes_to_read, bool* pEOF_flag, void* userData) @system 163 { 164 JPEGIOHandle* jio = cast(JPEGIOHandle*) userData; 165 size_t read = jio.wrapped.read(pBuf, 1, max_bytes_to_read, jio.handle); 166 if (pEOF_flag) 167 { 168 *pEOF_flag = jio.wrapped.eof(jio.handle) != 0; 169 } 170 assert(read >= 0 && read <= 0x7fff_ffff); 171 return cast(int) read; 172 } 173 174 // Note: context is a user pointer on a JPEGIOHandle. 175 void stb_stream_write(void *context, const(void)* data, int size) @system 176 { 177 JPEGIOHandle* jio = cast(JPEGIOHandle*) context; 178 179 if (jio.errored) 180 return; 181 182 size_t written = jio.wrapped.write(data, 1, size, jio.handle); 183 if (written != size) 184 jio.errored = true; // poison the JPEGIOHandleB 185 }