1 /// Reads a jpg header without reading the rest of the file. Use [arsd.jpeg] for an actual image loader if you need actual data, but this is kept around for times when you only care about basic info like image dimensions. 2 module arsd.jpg; 3 4 import std.typecons; 5 import std.stdio; 6 import std.conv; 7 8 struct JpegSection { 9 ubyte identifier; 10 ubyte[] data; 11 } 12 13 // gives as a range of file sections 14 struct LazyJpegFile { 15 File f; 16 JpegSection _front; 17 bool _frontIsValid; 18 this(File f) { 19 this.f = f; 20 21 ubyte[2] headerBuffer; 22 auto data = f.rawRead(headerBuffer); 23 if(data != [0xff, 0xd8]) 24 throw new Exception("no jpeg header"); 25 popFront(); // prime 26 } 27 28 void popFront() { 29 ubyte[4] startingBuffer; 30 auto read = f.rawRead(startingBuffer); 31 if(read.length != 4) { 32 _frontIsValid = false; 33 return; // end of file 34 } 35 36 if(startingBuffer[0] != 0xff) 37 throw new Exception("not lined up in file"); 38 39 _front.identifier = startingBuffer[1]; 40 ushort length = cast(ushort) (startingBuffer[2]) * 256 + startingBuffer[3]; 41 42 if(length < 2) 43 throw new Exception("wtf"); 44 length -= 2; // the length in the file includes the block header, but we just want the data here 45 46 _front.data = new ubyte[](length); 47 read = f.rawRead(_front.data); 48 if(read.length != length) 49 throw new Exception("didn't read the file right, got " ~ to!string(read.length) ~ " instead of " ~ to!string(length)); 50 51 _frontIsValid = true; 52 } 53 54 JpegSection front() { 55 return _front; 56 } 57 58 bool empty() { 59 return !_frontIsValid; 60 } 61 } 62 63 // returns width, height 64 Tuple!(int, int) getSizeFromFile(string filename) { 65 import std.stdio; 66 67 auto file = File(filename, "rb"); 68 69 auto jpeg = LazyJpegFile(file); 70 71 auto firstSection = jpeg.front(); 72 jpeg.popFront(); 73 74 // commented because exif and jfif are both readable by this so no need to be picky 75 //if(firstSection.identifier != 0xe0) 76 //throw new Exception("bad header"); 77 78 for(; !jpeg.empty(); jpeg.popFront()) { 79 if(jpeg.front.identifier != 0xc0) 80 continue; 81 auto data = jpeg.front.data[1..$]; // skip the precision byte 82 83 ushort height = data[0] * 256 + data[1]; 84 ushort width = data[2] * 256 + data[3]; 85 return tuple(cast(int) width, cast(int) height); 86 } 87 88 throw new Exception("idk about the length"); 89 } 90 91 version(with_libjpeg) { 92 /+ 93 import arsd.color; 94 95 TrueColorImage read_JPEG_file(string filename) { 96 /* This struct contains the JPEG decompression parameters and pointers to 97 * working space (which is allocated as needed by the JPEG library). 98 */ 99 struct jpeg_decompress_struct cinfo; 100 /* We use our private extension JPEG error handler. 101 * Note that this struct must live as long as the main JPEG parameter 102 * struct, to avoid dangling-pointer problems. 103 */ 104 struct my_error_mgr jerr; 105 /* More stuff */ 106 FILE * infile; /* source file */ 107 JSAMPARRAY buffer; /* Output row buffer */ 108 int row_stride; /* physical row width in output buffer */ 109 110 /* In this example we want to open the input file before doing anything else, 111 * so that the setjmp() error recovery below can assume the file is open. 112 * VERY IMPORTANT: use "b" option to fopen() if you are on a machine that 113 * requires it in order to read binary files. 114 */ 115 116 if ((infile = fopen(filename, "rb")) == NULL) { 117 fprintf(stderr, "can't open %s\n", filename); 118 return 0; 119 } 120 121 /* Step 1: allocate and initialize JPEG decompression object */ 122 123 /* We set up the normal JPEG error routines, then override error_exit. */ 124 cinfo.err = jpeg_std_error(&jerr.pub); 125 jerr.pub.error_exit = my_error_exit; 126 /* Establish the setjmp return context for my_error_exit to use. */ 127 if (setjmp(jerr.setjmp_buffer)) { 128 /* If we get here, the JPEG code has signaled an error. 129 * We need to clean up the JPEG object, close the input file, and return. 130 */ 131 jpeg_destroy_decompress(&cinfo); 132 fclose(infile); 133 return 0; 134 } 135 /* Now we can initialize the JPEG decompression object. */ 136 jpeg_create_decompress(&cinfo); 137 138 /* Step 2: specify data source (eg, a file) */ 139 140 jpeg_stdio_src(&cinfo, infile); 141 142 /* Step 3: read file parameters with jpeg_read_header() */ 143 144 (void) jpeg_read_header(&cinfo, TRUE); 145 /* We can ignore the return value from jpeg_read_header since 146 * (a) suspension is not possible with the stdio data source, and 147 * (b) we passed TRUE to reject a tables-only JPEG file as an error. 148 * See libjpeg.txt for more info. 149 */ 150 151 /* Step 4: set parameters for decompression */ 152 153 /* In this example, we don't need to change any of the defaults set by 154 * jpeg_read_header(), so we do nothing here. 155 */ 156 157 /* Step 5: Start decompressor */ 158 159 (void) jpeg_start_decompress(&cinfo); 160 /* We can ignore the return value since suspension is not possible 161 * with the stdio data source. 162 */ 163 164 /* We may need to do some setup of our own at this point before reading 165 * the data. After jpeg_start_decompress() we have the correct scaled 166 * output image dimensions available, as well as the output colormap 167 * if we asked for color quantization. 168 * In this example, we need to make an output work buffer of the right size. 169 */ 170 /* JSAMPLEs per row in output buffer */ 171 row_stride = cinfo.output_width * cinfo.output_components; 172 /* Make a one-row-high sample array that will go away when done with image */ 173 buffer = (*cinfo.mem->alloc_sarray) 174 ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1); 175 176 /* Step 6: while (scan lines remain to be read) */ 177 /* jpeg_read_scanlines(...); */ 178 179 /* Here we use the library's state variable cinfo.output_scanline as the 180 * loop counter, so that we don't have to keep track ourselves. 181 */ 182 while (cinfo.output_scanline < cinfo.output_height) { 183 /* jpeg_read_scanlines expects an array of pointers to scanlines. 184 * Here the array is only one element long, but you could ask for 185 * more than one scanline at a time if that's more convenient. 186 */ 187 (void) jpeg_read_scanlines(&cinfo, buffer, 1); 188 /* Assume put_scanline_someplace wants a pointer and sample count. */ 189 put_scanline_someplace(buffer[0], row_stride); 190 } 191 192 /* Step 7: Finish decompression */ 193 194 (void) jpeg_finish_decompress(&cinfo); 195 /* We can ignore the return value since suspension is not possible 196 * with the stdio data source. 197 */ 198 199 /* Step 8: Release JPEG decompression object */ 200 201 /* This is an important step since it will release a good deal of memory. */ 202 jpeg_destroy_decompress(&cinfo); 203 204 /* After finish_decompress, we can close the input file. 205 * Here we postpone it until after no more JPEG errors are possible, 206 * so as to simplify the setjmp error logic above. (Actually, I don't 207 * think that jpeg_destroy can do an error exit, but why assume anything...) 208 */ 209 fclose(infile); 210 211 /* At this point you may want to check to see whether any corrupt-data 212 * warnings occurred (test whether jerr.pub.num_warnings is nonzero). 213 */ 214 215 /* And we're done! */ 216 return 1; 217 } 218 +/ 219 }