1 /** 2 BMP 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.bmp; 8 9 nothrow @nogc @safe: 10 11 import core.stdc.stdlib: malloc, free, realloc; 12 import gamut.types; 13 import gamut.io; 14 import gamut.plugin; 15 import gamut.image; 16 import gamut.internals.errors; 17 import gamut.internals.types; 18 19 20 version(decodeBMP) import gamut.codecs.stbdec; 21 version(encodeBMP) import gamut.codecs.bmpenc; 22 23 ImageFormatPlugin makeBMPPlugin() 24 { 25 ImageFormatPlugin p; 26 p.format = "BMP"; 27 p.extensionList = "bmp,dib"; 28 p.mimeTypes = "image/bmp"; 29 version(decodeBMP) 30 p.loadProc = &loadBMP; 31 else 32 p.loadProc = null; 33 version(encodeBMP) 34 p.saveProc = &saveBMP; 35 else 36 p.saveProc = null; 37 p.detectProc = &detectBMP; 38 return p; 39 } 40 41 // FUTURE: Note: detection API should report I/O errors other than yes/no for the test, 42 // since stream might be fatally errored. 43 // Returning a ternary would be extra-nice. 44 45 bool detectBMP(IOStream *io, IOHandle handle) @trusted 46 { 47 // save I/O cursor 48 c_long offset = io.tell(handle); 49 if (offset == -1) // IO error 50 return false; 51 52 uint ds; 53 bool err; 54 ubyte b = io.read_ubyte(handle, &err); if (err) return false; // IO error 55 if (b != 'B') goto no_match; 56 57 b = io.read_ubyte(handle, &err); if (err) return false; // IO error 58 if (b != 'M') goto no_match; 59 60 if (!io.skipBytes(handle, 12)) 61 return false; // IO error 62 63 ds = io.read_uint_LE(handle, &err); if (err) return false; // IO error 64 65 if (ds == 12 || ds == 40 || ds == 52 || ds == 56 || ds == 108 || ds == 124) 66 goto match; 67 else 68 goto no_match; 69 70 match: 71 // restore I/O cursor 72 if (!io.seekAbsolute(handle, offset)) 73 return false; // IO error 74 return true; 75 76 no_match: 77 // restore I/O cursor 78 if (!io.seekAbsolute(handle, offset)) 79 return false; // IO error 80 81 return false; 82 } 83 84 version(decodeBMP) 85 void loadBMP(ref Image image, IOStream *io, IOHandle handle, int page, int flags, void *data) @trusted 86 { 87 // prepare STB callbacks 88 IOAndHandle ioh; 89 stbi_io_callbacks stb_callback; 90 initSTBCallbacks(io, handle, &ioh, &stb_callback); 91 92 int requestedComp = computeRequestedImageComponents(flags); 93 if (requestedComp == 0) // error 94 { 95 image.error(kStrInvalidFlags); 96 return; 97 } 98 if (requestedComp == -1) 99 requestedComp = 0; // auto 100 101 ubyte* decoded; 102 int width, height, components; 103 104 float ppmX = -1; 105 float ppmY = -1; 106 float pixelRatio = -1; 107 108 // PERF: let stb_image return a flipped bitmap, so that to save some time on load. 109 110 decoded = stbi_load_from_callbacks(&stb_callback, &ioh, &width, &height, &components, requestedComp, 111 &ppmX, &ppmY, &pixelRatio); 112 113 if (requestedComp != 0) 114 components = requestedComp; 115 116 if (decoded is null) 117 { 118 image.error(kStrImageDecodingFailed); 119 return; 120 } 121 122 if (!imageIsValidSize(1, width, height)) 123 { 124 image.error(kStrImageTooLarge); 125 free(decoded); 126 return; 127 } 128 129 image._allocArea = decoded; // Note: coupling, works because stb and gamut both use malloc/free 130 image._width = width; 131 image._height = height; 132 image._data = decoded; 133 image._pitch = width * components; 134 image._pixelAspectRatio = (pixelRatio == -1) ? GAMUT_UNKNOWN_ASPECT_RATIO : pixelRatio; 135 image._resolutionY = (ppmY == -1) ? GAMUT_UNKNOWN_RESOLUTION : convertInchesToMeters(ppmY); 136 image._layoutConstraints = LAYOUT_DEFAULT; // STB decoder follows no particular constraints (TODO?) 137 image._layerCount = 1; 138 image._layerOffset = 0; 139 140 if (components == 1) 141 { 142 image._type = PixelType.l8; 143 } 144 else if (components == 2) 145 { 146 image._type = PixelType.la8; 147 } 148 else if (components == 3) 149 { 150 image._type = PixelType.rgb8; 151 } 152 else if (components == 4) 153 { 154 image._type = PixelType.rgba8; 155 } 156 else 157 assert(false); 158 159 PixelType targetType = applyLoadFlags(image._type, flags); 160 161 // Convert to target type and constraints 162 image.convertTo(targetType, cast(LayoutConstraints) flags); 163 } 164 165 version(encodeBMP) 166 bool saveBMP(ref const(Image) image, IOStream *io, IOHandle handle, int page, int flags, void *data) @trusted 167 { 168 if (page != 0) 169 return false; 170 171 int components; 172 173 // For now, can save RGB and RGBA 8-bit images. 174 switch (image._type) 175 { 176 case PixelType.rgb8: 177 components = 3; break; 178 case PixelType.rgba8: 179 components = 4; 180 break; 181 default: 182 return false; 183 } 184 185 int width = image._width; 186 int height = image._height; 187 int pitch = image._pitch; 188 if (width < 1 || height < 1 || width > 32767 || height > 32767) 189 return false; // Can't be saved as BMP 190 191 bool success = write_bmp(image, io, handle, width, height, components); 192 193 return success; 194 } 195