-
Notifications
You must be signed in to change notification settings - Fork 84
Expand file tree
/
Copy pathjpegloader.cpp
More file actions
476 lines (388 loc) · 14.2 KB
/
jpegloader.cpp
File metadata and controls
476 lines (388 loc) · 14.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================
#include "jpegloader.h"
#include "tier0/dbg.h"
#include "tier1/utlvector.h"
#include "tier0/vprof.h"
#include "jpeglib/jpeglib.h"
#include <setjmp.h>
//#include "fileio.h"
class CFileWriter
{
};
class CJpegDestMgr : public jpeg_destination_mgr
{
public:
CJpegDestMgr( CFileWriter &refOutputFileWriter )
: m_pOutputFileWriter( &refOutputFileWriter ),
m_pOutputBuffer( NULL )
{
Init();
}
CJpegDestMgr( CUtlBuffer &refOutputBuffer )
: m_pOutputFileWriter( NULL ),
m_pOutputBuffer( &refOutputBuffer )
{
Init();
}
void Init()
{
this->init_destination = &CJpegDestMgr::imp_init_dest;
this->empty_output_buffer = &CJpegDestMgr::imp_empty_output_buffer;
this->term_destination = &CJpegDestMgr::imp_term_destination;
this->next_output_byte = 0;
this->free_in_buffer = 0;
}
static void imp_init_dest( j_compress_ptr cinfo )
{
CJpegDestMgr *pInstance = (CJpegDestMgr*)cinfo->dest;
if ( pInstance->m_pOutputBuffer )
pInstance->m_pOutputBuffer->EnsureCapacity( cinfo->image_width*cinfo->image_height*3 );
const int k_cubMaxTempJpegBuffer = 1024*10;
int cubBufferSize = MIN( cinfo->image_width*cinfo->image_height*3, k_cubMaxTempJpegBuffer );
pInstance->m_Buffer.EnsureCapacity( cubBufferSize );
pInstance->free_in_buffer = pInstance->m_Buffer.Size();
pInstance->next_output_byte = (JOCTET*)pInstance->m_Buffer.Base();
}
static boolean imp_empty_output_buffer( j_compress_ptr cinfo )
{
/*
CJpegDestMgr *pInstance = (CJpegDestMgr*)cinfo->dest;
// Write the entire buffer, ignoring next_output_byte and free_in_buffer as they lie (see docs)
if ( pInstance->m_pOutputFileWriter )
pInstance->m_pOutputFileWriter->Write( pInstance->m_Buffer.Base(), pInstance->m_Buffer.Size() );
else
pInstance->m_pOutputBuffer->Put( pInstance->m_Buffer.Base(), pInstance->m_Buffer.Size() );
pInstance->free_in_buffer = pInstance->m_Buffer.Size();
pInstance->next_output_byte = (JOCTET*)pInstance->m_Buffer.Base();
*/
return true;
}
static void imp_term_destination(j_compress_ptr cinfo)
{
/*
CJpegDestMgr *pInstance = (CJpegDestMgr*)cinfo->dest;
// next_output_byte and free_in_buffer don't lie here like in empty_output_buffer
int cubToWrite = (byte*)pInstance->next_output_byte - (byte*)pInstance->m_Buffer.Base();
if ( cubToWrite > 0 )
{
if ( pInstance->m_pOutputFileWriter )
pInstance->m_pOutputFileWriter->Write( pInstance->m_Buffer.Base(), cubToWrite );
else
pInstance->m_pOutputBuffer->Put( pInstance->m_Buffer.Base(), cubToWrite );
}
if ( pInstance->m_pOutputFileWriter )
pInstance->m_pOutputFileWriter->Flush();
*/
}
static void error_exit( j_common_ptr cptr )
{
longjmp( m_JmpBuf, 1 );
}
static jmp_buf m_JmpBuf;
private:
CFileWriter *m_pOutputFileWriter;
CUtlBuffer *m_pOutputBuffer;
CUtlBuffer m_Buffer;
};
jmp_buf CJpegDestMgr::m_JmpBuf;
class CJpegSourceMgr : public jpeg_source_mgr
{
public:
CJpegSourceMgr()
{
this->init_source = &CJpegSourceMgr::imp_init_source;
this->fill_input_buffer = &CJpegSourceMgr::imp_fill_input_buffer;
this->skip_input_data = &CJpegSourceMgr::imp_skip_input_data;
this->resync_to_restart = &CJpegSourceMgr::imp_resync_to_restart;
this->term_source = &CJpegSourceMgr::imp_term_source;
this->next_input_byte = 0;
this->bytes_in_buffer = 0;
}
bool Init( const byte *pubData, int cubData )
{
m_Data.AddMultipleToTail( cubData, (char *)pubData );
bytes_in_buffer = m_Data.Count();
next_input_byte = (unsigned char*)m_Data.Base();
return true;
}
static void imp_init_source(j_decompress_ptr cinfo)
{
// We handled everything needed in our own Init() call.
}
static boolean imp_fill_input_buffer(j_decompress_ptr cinfo)
{
// If this is hit it means we are reading a corrupt file. We can't provide more data
// as we provided all of it upfront already.
return 0;
}
static void imp_skip_input_data(j_decompress_ptr cinfo, long num_bytes)
{
// The library is asking us to skip a chunk of data, usually exif header data or such. Need
// to actually do that. The library tries to be robust and skip obviously bad data on its own
// one byte at a time as well, but faster here, and safer as the library can't always do it
// correctly if we fail these calls.
CJpegSourceMgr *pInstance = (CJpegSourceMgr*)cinfo->src;
pInstance->bytes_in_buffer -= num_bytes;
pInstance->next_input_byte += num_bytes;
}
static boolean imp_resync_to_restart(j_decompress_ptr cinfo, int desired)
{
// This happens if the library fails to find a resync marker where expected, we'll then
// use it's default handler. This handler will skip ahead trying to find the next point,
// we could maybe do better going backwards as we have the full buffer, but that's lots more
// work and this will only get hit on a partially corrupt image anyway.
return jpeg_resync_to_restart( cinfo, desired );
}
static void imp_term_source(j_decompress_ptr cinfo)
{
}
static void error_exit( j_common_ptr cptr )
{
longjmp( m_JmpBuf, 1 );
}
static jmp_buf m_JmpBuf;
public:
CUtlVector<char> m_Data;
};
jmp_buf CJpegSourceMgr::m_JmpBuf;
//-----------------------------------------------------------------------------
// Purpose: returns the dimensions of a jpg file from it's contents
//-----------------------------------------------------------------------------
bool GetJpegDimensions( const byte *pubData, int cubData, uint32 &width, uint32 &height )
{
CJpegSourceMgr sourceMgr;
bool bRet = sourceMgr.Init( pubData, cubData );
if ( !bRet )
return false;
// Load the jpeg.
struct jpeg_decompress_struct jpegInfo;
struct jpeg_error_mgr jerr;
memset( &jpegInfo, 0, sizeof( jpegInfo ) );
jpegInfo.err = jpeg_std_error(&jerr);
jerr.error_exit = &CJpegSourceMgr::error_exit;
jpeg_create_decompress(&jpegInfo);
jpegInfo.src = &sourceMgr;
if ( setjmp( sourceMgr.m_JmpBuf ) == 1 )
{
jpeg_destroy_decompress(&jpegInfo);
return false;
}
if (jpeg_read_header(&jpegInfo, TRUE) != JPEG_HEADER_OK)
{
jpeg_destroy_decompress(&jpegInfo);
return false;
}
width = jpegInfo.image_width;
height = jpegInfo.image_height;
jpeg_destroy_decompress(&jpegInfo);
return true;
}
//-----------------------------------------------------------------------------
// Purpose: takes the contents of a .jpg file and turns it into RGB data, returning the width and height and optionally the number of bytes used
//-----------------------------------------------------------------------------
bool ConvertJpegToRGB( const byte *pubData, int cubData, CUtlVector<byte> &buf, int &width, int &height, int *pcubUsed )
{
CJpegSourceMgr sourceMgr;
bool bRet = sourceMgr.Init( pubData, cubData );
if ( !bRet )
return false;
if ( pcubUsed )
*pcubUsed = 0;
sourceMgr.bytes_in_buffer = sourceMgr.m_Data.Count();
sourceMgr.next_input_byte = (unsigned char*)sourceMgr.m_Data.Base();
// Load the jpeg.
struct jpeg_decompress_struct jpegInfo;
struct jpeg_error_mgr jerr;
memset( &jpegInfo, 0, sizeof( jpegInfo ) );
jpegInfo.err = jpeg_std_error(&jerr);
jerr.error_exit = &CJpegSourceMgr::error_exit;
jpeg_create_decompress(&jpegInfo);
jpegInfo.src = &sourceMgr;
if ( setjmp( sourceMgr.m_JmpBuf ) == 1 )
{
jpeg_destroy_decompress(&jpegInfo);
return false;
}
if (jpeg_read_header(&jpegInfo, TRUE) != JPEG_HEADER_OK)
{
jpeg_destroy_decompress(&jpegInfo);
return false;
}
// start the decompress with the jpeg engine.
if ( !jpeg_start_decompress(&jpegInfo) || jpegInfo.output_components != 3)
{
jpeg_destroy_decompress(&jpegInfo);
return false;
}
// now that we've started the decompress with the jpeg lib, we have the attributes of the
// cdnfile ready to be read out of the decompress struct.
int row_stride = jpegInfo.output_width * jpegInfo.output_components;
int mem_required = jpegInfo.image_height * jpegInfo.image_width * jpegInfo.output_components;
JSAMPROW row_pointer[1];
int cur_row = 0;
width = jpegInfo.output_width;
height = jpegInfo.output_height;
// allocate the memory to read the cdnfile data into.
buf.SetSize( mem_required );
// read in all the scan lines of the cdnfile into our cdnfile data buffer.
bool working = true;
while (working && (jpegInfo.output_scanline < jpegInfo.output_height))
{
row_pointer[0] = &(buf[cur_row * row_stride]);
if ( !jpeg_read_scanlines(&jpegInfo, row_pointer, 1) )
{
working = false;
}
++cur_row;
}
if (!working)
{
jpeg_destroy_decompress(&jpegInfo);
return false;
}
if ( pcubUsed )
*pcubUsed = (int)((byte*)sourceMgr.next_input_byte - (byte*)sourceMgr.m_Data.Base());
jpeg_finish_decompress(&jpegInfo);
jpeg_destroy_decompress(&jpegInfo);
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Converts an RGB image to RGBA opacity will be 100%
//-----------------------------------------------------------------------------
void ConvertRGBToRGBAImage( CUtlVector<unsigned char> &srcData,
int srcWidth,
int srcHeight,
byte *destData,
int destWidth,
int destHeight )
{
int destPixelSize = 4;
int cub = destWidth * destHeight * destPixelSize;
byte *pSrc = srcData.Base();
for ( int i = 0; i < cub; i += 4 )
{
destData[i] = *pSrc++;
destData[i+1] = *pSrc++;
destData[i+2] = *pSrc++;
destData[i+3] = 255;
}
}
//-----------------------------------------------------------------------------
// Purpose: Converts raw Jpeg file data and converts to RGBA image buffer
//-----------------------------------------------------------------------------
bool ConvertJpegToRawInternal( const byte *pubJpegData, int cubJpegData, CUtlBuffer &bufOutput, int &width, int &height, int *pcubUsed, bool bMakeRGBA )
{
// Temporary buffer to decompress Jpeg into as RGB, since libjpeg doesn't do RGBA
CUtlVector<byte> vecRGB;
bool bConverted = false;
{
VPROF_BUDGET( "ConvertJpegToRGB", VPROF_BUDGETGROUP_OTHER_VGUI );
bConverted = ConvertJpegToRGB( pubJpegData, cubJpegData, vecRGB, width, height, pcubUsed );
}
if ( bConverted )
{
bufOutput.Clear();
int bytesPerPixel = 3;
if ( bMakeRGBA )
bytesPerPixel = 4;
// make sure the buffer is big enough to hold the cdnfile data
bufOutput.EnsureCapacity( width * height * bytesPerPixel );
// If the buffer was externally allocated the EnsureCapacity can fail
if ( bufOutput.Size() < width * height * bytesPerPixel )
return false;
bufOutput.SeekPut( CUtlBuffer::SEEK_HEAD, width*height*bytesPerPixel );
// convert RGB->RGBA, into the final buffer
if ( bMakeRGBA )
{
VPROF_BUDGET( "ConvertRGBToRGBAImage", VPROF_BUDGETGROUP_OTHER_VGUI );
ConvertRGBToRGBAImage( vecRGB, width, height, (byte *)bufOutput.Base(), width, height );
}
else
{
Q_memcpy( bufOutput.Base(), vecRGB.Base(), width*height*bytesPerPixel );
}
// done
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose: Converts raw Jpeg file data and converts to RGBA image buffer
//-----------------------------------------------------------------------------
bool ConvertJpegToRGBA( const byte *pubJpegData, int cubJpegData, CUtlBuffer &bufOutput, int &width, int &height, int *pcubUsed )
{
return ConvertJpegToRawInternal( pubJpegData, cubJpegData, bufOutput, width, height, pcubUsed, true );
}
//-----------------------------------------------------------------------------
// Purpose: Converts raw Jpeg file data and converts to RGB image buffer
//-----------------------------------------------------------------------------
bool ConvertJpegToRGB( const byte *pubJpegData, int cubJpegData, CUtlBuffer &bufOutput, int &width, int &height, int *pcubUsed )
{
return ConvertJpegToRawInternal( pubJpegData, cubJpegData, bufOutput, width, height, pcubUsed, false );
}
//-----------------------------------------------------------------------------
// Purpose: Internal method for converting RGB to Jpeg and writing either
// to disk or to an output buffer
//-----------------------------------------------------------------------------
bool ConvertRGBToJpegInternal( CJpegDestMgr &destMgr, int quality, int width, int height, CUtlBuffer &bufRGB )
{
struct jpeg_compress_struct cinfo = {0};
JSAMPROW row_ptr[1];
struct jpeg_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr);
jerr.error_exit = &CJpegDestMgr::error_exit;
jpeg_create_compress(&cinfo);
cinfo.dest = &destMgr;
if ( setjmp( destMgr.m_JmpBuf ) == 1 )
{
jpeg_destroy_compress(&cinfo);
return false;
}
cinfo.image_width = width;
cinfo.image_height = height;
cinfo.input_components = 3;
cinfo.in_color_space = JCS_RGB;
jpeg_set_defaults( &cinfo );
cinfo.dct_method = JDCT_FLOAT;
jpeg_set_quality(&cinfo, quality, TRUE);
jpeg_start_compress( &cinfo, TRUE );
byte *rawBytes = (byte*)bufRGB.Base();
while( cinfo.next_scanline < cinfo.image_height )
{
row_ptr[0] = (unsigned char *)rawBytes+(width*3*cinfo.next_scanline);
jpeg_write_scanlines( &cinfo, row_ptr, 1 );
}
jpeg_finish_compress( &cinfo );
jpeg_destroy_compress( &cinfo );
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Takes a RGB buffer and writes it to disk as a Jpeg
// Params: qualtity should be 0-100, where 100 is best quality. 80 is generally
// a good value.
//-----------------------------------------------------------------------------
bool ConvertRGBToJpeg( const char *pchFileOut, int quality, int width, int height, CUtlBuffer &bufRGB )
{
/*
CFileWriter fileWriter;
if ( !fileWriter.BSetFile( pchFileOut ) )
return false;
CJpegDestMgr destMgr( fileWriter );
return ConvertRGBToJpegInternal( destMgr, quality, width, height, bufRGB );
*/
return false;
}
//-----------------------------------------------------------------------------
// Purpose: Takes a RGB buffer and writes it to a buffer as a jpeg
// Params: qualtity should be 0-100, where 100 is best quality. 80 is generally
// a good value.
//-----------------------------------------------------------------------------
bool ConvertRGBToJpeg( CUtlBuffer &bufOutput, int quality, int width, int height, CUtlBuffer &bufRGB )
{
CJpegDestMgr destMgr( bufOutput );
return ConvertRGBToJpegInternal( destMgr, quality, width, height, bufRGB );
}