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 }