|
| 1 | +// BMP-reading code. Much of this is adapted from the Adafruit_ImageReader |
| 2 | +// library, which is extremely LCD-display-centric and turned out to be |
| 3 | +// neither workable nor easily extensible into what was needed here. That's |
| 4 | +// okay. For now, I just pulled in minimal parts of that code, with changes |
| 5 | +// as needed for this project (and dropping some portability considerations). |
| 6 | + |
| 7 | +#include <SdFat.h> |
| 8 | + |
| 9 | +// Barebones query pixel height of BMP image file. NOT universally portable; |
| 10 | +// does some rude straight-to-var little-endian reads. |
| 11 | +// Param: Pointer to FAT volume. |
| 12 | +// Param: Absolute filename. |
| 13 | +// Param: Pointer to int32_t for height result. |
| 14 | +// Returns: true on success, false on ANY error, does not distinguish |
| 15 | +// (file not found, no BMP signature, etc.). |
| 16 | +bool bmpHeight(FatVolume *fs, char *filename, int32_t *height) { |
| 17 | + File32 file; |
| 18 | + bool status = false; // Assume error until success |
| 19 | + if ((file = fs->open(filename, FILE_READ))) { |
| 20 | + uint16_t sig; |
| 21 | + file.read(&sig, sizeof sig); // Little-endian straight to var |
| 22 | + if (sig == 0x4D42) { // BMP signature? |
| 23 | + file.seekCur(20); // Skip file size, width, etc. |
| 24 | + file.read(height, sizeof(int32_t)); // Little-endian straight to var |
| 25 | + if (*height < 0) *height *= -1; // Handle top-to-bottom variant |
| 26 | + status = true; // YAY |
| 27 | + } |
| 28 | + file.close(); |
| 29 | + } |
| 30 | + return status; |
| 31 | +} |
| 32 | + |
| 33 | +// Barebones BMP read into RAM. ALL images regardless of BMP format are |
| 34 | +// converted as needed into a DotStar-ready 24-bit-per-pixel format. Again |
| 35 | +// this is NOT universally portable; rude little-endian reads. |
| 36 | +// Param: Pointer to FAT volume. |
| 37 | +// Param: Absolute filename. |
| 38 | +// Param: Pointer to uint8_t for storing result (destination buffer |
| 39 | +// is assumed allocated and cleared, not performed here). |
| 40 | +// Param: Max width to clip or pad to (DotStar strip length). |
| 41 | +// Param: Red, green, blue byte offsets (0-2) in dest buffer. |
| 42 | +// Param: Brightness (0.0 to 255.0). |
| 43 | +// Param: Gamma (1.0 = linear, 2.6 = typical LED curve). Gamma is a lossy |
| 44 | +// operation; might prefer to pass 1.0 and do adjustment on source |
| 45 | +// image instead w/dithering, or perhaps future poi code could do |
| 46 | +// this on-the-fly. But for now, on load. |
| 47 | +// Returns: true on success, false on ANY error, does not distinguish |
| 48 | +// (file not found, no BMP signature, etc.). |
| 49 | +bool loadBMP(FatVolume *fs, char *filename, uint8_t *dest, |
| 50 | + const uint16_t dest_width, const uint8_t rOffset, |
| 51 | + const uint8_t gOffset, const uint8_t bOffset, |
| 52 | + const float brightness, const float gamma) { |
| 53 | + File32 file; |
| 54 | + bool status = false; // Assume error until success |
| 55 | + if ((file = fs->open(filename, FILE_READ))) { |
| 56 | + uint16_t sig; |
| 57 | + file.read(&sig, sizeof sig); // Little-endian straight to var |
| 58 | + if (sig == 0x4D42) { // BMP signature? |
| 59 | + uint32_t offset; // Start of image data |
| 60 | + uint32_t header_size; // Indicates BMP version |
| 61 | + int32_t bmp_width, bmp_height; // BMP width & height in pixels |
| 62 | + boolean flip = true; // BMP is stored bottom-to-top |
| 63 | + uint32_t compression = 0; // BMP compression mode |
| 64 | + uint32_t colors = 0; // Number of colors in palette |
| 65 | + uint16_t planes; |
| 66 | + uint16_t depth; |
| 67 | + |
| 68 | + file.seekCur(8); // Skip file size, creator bytes |
| 69 | + file.read(&offset , sizeof offset); |
| 70 | + file.read(&header_size, sizeof header_size); // DIB header... |
| 71 | + file.read(&bmp_width , sizeof bmp_width); |
| 72 | + file.read(&bmp_height , sizeof bmp_height); |
| 73 | + // If bmpHeight is negative, image is in top-down order. |
| 74 | + // This is not canon but has been observed in the wild. |
| 75 | + if (bmp_height < 0) { |
| 76 | + bmp_height *= -1; |
| 77 | + flip = false; |
| 78 | + } |
| 79 | + file.read(&planes, sizeof planes); |
| 80 | + file.read(&depth , sizeof depth); |
| 81 | + // Compression mode is present in later BMP versions (default = none) |
| 82 | + if (header_size > 12) { |
| 83 | + file.read(&compression, sizeof compression); |
| 84 | + file.seekCur(12); // Skip raw bitmap data size, etc. |
| 85 | + file.read(&colors, sizeof colors); // # of colors in palette; 0 = 2^depth |
| 86 | + file.seekCur(4); // Skip # of colors used |
| 87 | + // File position should now be at start of palette (if present) |
| 88 | + } |
| 89 | + |
| 90 | + if ((planes == 1) && (compression == 0)) { // Only uncompressed is handled |
| 91 | + uint8_t palette[3][256]; // Rude but code's easier than malloc check |
| 92 | + uint8_t b; // Byte-holding var used in 1-8 bit modes |
| 93 | + |
| 94 | + if (depth < 16) { // Lower depths include a color palette |
| 95 | + if (!colors) colors = 1 << depth; |
| 96 | + for (uint16_t i = 0; i < colors; i++) { |
| 97 | + uint32_t rgb; |
| 98 | + file.read(&rgb, sizeof rgb); |
| 99 | + palette[rOffset][i] = (uint8_t)(pow((float)((rgb >> 16) & 0xFF) / 255.0, gamma) * brightness + 0.5); |
| 100 | + palette[gOffset][i] = (uint8_t)(pow((float)((rgb >> 8) & 0xFF) / 255.0, gamma) * brightness + 0.5); |
| 101 | + palette[bOffset][i] = (uint8_t)(pow((float)( rgb & 0xFF) / 255.0, gamma) * brightness + 0.5); |
| 102 | + } |
| 103 | + } else { |
| 104 | + // But HEY, as long as we have that palette array taking up space |
| 105 | + // on the heap...use it to pre-compute a brightness/gamma table, |
| 106 | + // saves a TON of floating-point math on every pixel later. |
| 107 | + for (uint16_t i = 0; i < 256; i++) { |
| 108 | + palette[0][i] = (uint8_t)(pow((float)i / 255.0, gamma) * brightness + 0.5); |
| 109 | + } |
| 110 | + } |
| 111 | + |
| 112 | + // BMP rows are padded (if needed) to 4-byte boundary, |
| 113 | + // width loaded is cropped if needed to DotStar strand length. |
| 114 | + uint32_t row_size = ((depth * bmp_width + 31) / 32) * 4; |
| 115 | + int load_width = min(bmp_width, dest_width); |
| 116 | + |
| 117 | + for (int row = 0; row < bmp_height; row++) { // For each scanline... |
| 118 | + |
| 119 | + file.seekSet(offset + row_size * (flip ? bmp_height - 1 - row : row)); |
| 120 | + uint8_t *d2 = dest + row * dest_width * 3; |
| 121 | + |
| 122 | + switch (depth) { |
| 123 | + case 32: |
| 124 | + for (int col = 0; col < load_width; col++, d2 += 3) { |
| 125 | + uint32_t rgba; |
| 126 | + file.read(&rgba, sizeof rgba); |
| 127 | + d2[rOffset] = palette[0][(rgba >> 16) & 0xFF]; // palette[0] is |
| 128 | + d2[gOffset] = palette[0][(rgba >> 8) & 0xFF]; // brightness/gamma |
| 129 | + d2[bOffset] = palette[0][ rgba & 0xFF]; // adjustment table |
| 130 | + } |
| 131 | + break; |
| 132 | + case 24: |
| 133 | + for (int col = 0; col < load_width; col++, d2 += 3) { |
| 134 | + d2[bOffset] = palette[0][file.read()]; // palette[0] is |
| 135 | + d2[gOffset] = palette[0][file.read()]; // brightness/gamma |
| 136 | + d2[rOffset] = palette[0][file.read()]; // adjustment table |
| 137 | + } |
| 138 | + break; |
| 139 | + case 16: |
| 140 | + // Not currently supported but might be nice to have. |
| 141 | + // Will require dissecting DIB header bitfields. |
| 142 | + break; |
| 143 | + case 8: |
| 144 | + for (int col = 0; col < load_width; col++, d2 += 3) { |
| 145 | + b = file.read(); |
| 146 | + d2[0] = palette[0][b]; |
| 147 | + d2[1] = palette[1][b]; |
| 148 | + d2[2] = palette[2][b]; |
| 149 | + } |
| 150 | + break; |
| 151 | + case 4: |
| 152 | + for (int col = 0; col < load_width; col++, d2 += 3) { |
| 153 | + uint8_t n; // 4-bit pixel value |
| 154 | + if (!(col & 1)) { |
| 155 | + b = file.read(); |
| 156 | + n = b >> 4; |
| 157 | + } else { |
| 158 | + n = b & 0xF; |
| 159 | + } |
| 160 | + d2[0] = palette[0][n]; |
| 161 | + d2[1] = palette[1][n]; |
| 162 | + d2[2] = palette[2][n]; |
| 163 | + } |
| 164 | + break; |
| 165 | + // A 2-bit BMP mode exists but is SUPER ESOTERIC (apparently |
| 166 | + // a Windows CE thing), not supported here (nor in Photoshop). |
| 167 | + case 2: |
| 168 | + break; |
| 169 | + case 1: |
| 170 | + for (int col = 0; col < load_width; col++, d2 += 3) { |
| 171 | + if (!(col & 7)) b = file.read(); |
| 172 | + uint8_t n = (b >> (7 - (col & 7))) & 1; |
| 173 | + d2[0] = palette[0][n]; |
| 174 | + d2[1] = palette[1][n]; |
| 175 | + d2[2] = palette[2][n]; |
| 176 | + } |
| 177 | + break; |
| 178 | + } // end switch |
| 179 | + |
| 180 | + } // end row |
| 181 | + status = true; // YAY |
| 182 | + } // end planes/compression check |
| 183 | + } // end signature |
| 184 | + file.close(); |
| 185 | + } // end file open |
| 186 | + return status; |
| 187 | +} |
0 commit comments