自己学习了下JPEG理论知识以后,找了个简单的解码器(NanoJPEG)试试看,原始地址在这里http://keyj.emphy.de/nanojpeg/,短短几百行,还是比较容易看懂的,本人理解详细参见代码注释,如理解有误欢迎指出,有问题/兴趣也可以留言和我讨论。
$ gcc -O3 -D_NJ_EXAMPLE_PROGRAM -o nanojpeg nanojpeg.c $ ./nanojpeg testorig.jpg
跟着main函数,边理论边实践,不错的方法!
// NanoJPEG -- KeyJ's Tiny Baseline JPEG Decoder // version 1.3 (2012-03-05) // by Martin J. Fiedler <martin.fiedler@gmx.net> // // This software is published under the terms of KeyJ's Research License, // version 0.2. Usage of this software is subject to the following conditions: // 0. There's no warranty whatsoever. The author(s) of this software can not // be held liable for any damages that occur when using this software. // 1. This software may be used freely for both non-commercial and commercial // purposes. // 2. This software may be redistributed freely as long as no fees are charged // for the distribution and this license information is included. // 3. This software may be modified freely except for this license information, // which must not be changed in any way. // 4. If anything other than configuration, indentation or comments have been // altered in the code, the original author(s) must receive a copy of the // modified code. /////////////////////////////////////////////////////////////////////////////// // DOCUMENTATION SECTION // // read this if you want to know what this is all about // /////////////////////////////////////////////////////////////////////////////// // INTRODUCTION // ============ // // This is a minimal decoder for baseline JPEG images. It accepts memory dumps // of JPEG files as input and generates either 8-bit grayscale or packed 24-bit // RGB images as output. It does not parse JFIF or Exif headers; all JPEG files // are assumed to be either grayscale or YCbCr. CMYK or other color spaces are // not supported. All YCbCr subsampling schemes with power-of-two ratios are // supported, as are restart intervals. Progressive or lossless JPEG is not // supported. // Summed up, NanoJPEG should be able to decode all images from digital cameras // and most common forms of other non-progressive JPEG images. // The decoder is not optimized for speed, it's optimized for simplicity and // small code. Image quality should be at a reasonable level. A bicubic chroma // upsampling filter ensures that subsampled YCbCr images are rendered in // decent quality. The decoder is not meant to deal with broken JPEG files in // a graceful manner; if anything is wrong with the bitstream, decoding will // simply fail. // The code should work with every modern C compiler without problems and // should not emit any warnings. It uses only (at least) 32-bit integer // arithmetic and is supposed to be endianness independent and 64-bit clean. // However, it is not thread-safe. // COMPILE-TIME CONFIGURATION // ========================== // // The following aspects of NanoJPEG can be controlled with preprocessor // defines: // // _NJ_EXAMPLE_PROGRAM = Compile a main() function with an example // program. // _NJ_INCLUDE_HEADER_ONLY = Don't compile anything, just act as a header // file for NanoJPEG. Example: // #define _NJ_INCLUDE_HEADER_ONLY // #include "nanojpeg.c" // int main(void) { // njInit(); // // your code here // njDone(); // } // NJ_USE_LIBC=1 = Use the malloc(), free(), memset() and memcpy() // functions from the standard C library (default). // NJ_USE_LIBC=0 = Don't use the standard C library. In this mode, // external functions njAlloc(), njFreeMem(), // njFillMem() and njCopyMem() need to be defined // and implemented somewhere. // NJ_USE_WIN32=0 = Normal mode (default). // NJ_USE_WIN32=1 = If compiling with MSVC for Win32 and // NJ_USE_LIBC=0, NanoJPEG will use its own // implementations of the required C library // functions (default if compiling with MSVC and // NJ_USE_LIBC=0). // NJ_CHROMA_FILTER=1 = Use the bicubic chroma upsampling filter // (default). // 图像resize的一种算法 // NJ_CHROMA_FILTER=0 = Use simple pixel repetition for chroma upsampling // (bad quality, but faster and less code). // API // === // // For API documentation, read the "header section" below. // EXAMPLE // ======= // // A few pages below, you can find an example program that uses NanoJPEG to // convert JPEG files into PGM or PPM. To compile it, use something like // gcc -O3 -D_NJ_EXAMPLE_PROGRAM -o nanojpeg nanojpeg.c // You may also add -std=c99 -Wall -Wextra -pedantic -Werror, if you want /////////////////////////////////////////////////////////////////////////////// // HEADER SECTION // // copy and pase this into nanojpeg.h if you want // /////////////////////////////////////////////////////////////////////////////// #ifndef _NANOJPEG_H #define _NANOJPEG_H // nj_result_t: Result codes for njDecode(). typedef enum _nj_result { NJ_OK = 0, // no error, decoding successful NJ_NO_JPEG, // not a JPEG file NJ_UNSUPPORTED, // unsupported format NJ_OUT_OF_MEM, // out of memory NJ_INTERNAL_ERR, // internal error NJ_SYNTAX_ERROR, // syntax error __NJ_FINISHED, // used internally, will never be reported } nj_result_t; // njInit: Initialize NanoJPEG. // For safety reasons, this should be called at least one time before using // using any of the other NanoJPEG functions. void njInit(void); // njDecode: Decode a JPEG image. // Decodes a memory dump of a JPEG file into internal buffers. // Parameters: // jpeg = The pointer to the memory dump. // size = The size of the JPEG file. // Return value: The error code in case of failure, or NJ_OK (zero) on success. nj_result_t njDecode(const void* jpeg, const int size); // njGetWidth: Return the width (in pixels) of the most recently decoded // image. If njDecode() failed, the result of njGetWidth() is undefined. int njGetWidth(void); // njGetHeight: Return the height (in pixels) of the most recently decoded // image. If njDecode() failed, the result of njGetHeight() is undefined. int njGetHeight(void); // njIsColor: Return 1 if the most recently decoded image is a color image // (RGB) or 0 if it is a grayscale image. If njDecode() failed, the result // of njGetWidth() is undefined. int njIsColor(void); // njGetImage: Returns the decoded image data. // Returns a pointer to the most recently image. The memory layout it byte- // oriented, top-down, without any padding between lines. Pixels of color // images will be stored as three consecutive bytes for the red, green and // blue channels. This data format is thus compatible with the PGM or PPM // file formats and the OpenGL texture formats GL_LUMINANCE8 or GL_RGB8. // If njDecode() failed, the result of njGetImage() is undefined. unsigned char* njGetImage(void); // njGetImageSize: Returns the size (in bytes) of the image data returned // by njGetImage(). If njDecode() failed, the result of njGetImageSize() is // undefined. int njGetImageSize(void); // njDone: Uninitialize NanoJPEG. // Resets NanoJPEG's internal state and frees all memory that has been // allocated at run-time by NanoJPEG. It is still possible to decode another // image after a njDone() call. void njDone(void); #endif//_NANOJPEG_H /////////////////////////////////////////////////////////////////////////////// // CONFIGURATION SECTION // // adjust the default settings for the NJ_ defines here // /////////////////////////////////////////////////////////////////////////////// #ifndef NJ_USE_LIBC #define NJ_USE_LIBC 1 #endif #ifndef NJ_USE_WIN32 #ifdef _MSC_VER #define NJ_USE_WIN32 (!NJ_USE_LIBC) #else #define NJ_USE_WIN32 0 #endif #endif #ifndef NJ_CHROMA_FILTER #define NJ_CHROMA_FILTER 1 #endif /////////////////////////////////////////////////////////////////////////////// // EXAMPLE PROGRAM // // just define _NJ_EXAMPLE_PROGRAM to compile this (requires NJ_USE_LIBC) // /////////////////////////////////////////////////////////////////////////////// #ifdef _NJ_EXAMPLE_PROGRAM #include <stdio.h> #include <stdlib.h> #include <string.h> int main(int argc, char* argv[]) { int size; char *buf; FILE *f; if (argc < 2) { printf("Usage: %s <input.jpg> [<output.ppm>]\n", argv[0]); return 2; } f = fopen(argv[1], "rb"); if (!f) { printf("Error opening the input file.\n"); return 1; } fseek(f, 0, SEEK_END); size = (int) ftell(f); // 字节 buf = malloc(size); fseek(f, 0, SEEK_SET); size = (int) fread(buf, 1, size, f); // 读取整个文件内容到buf fclose(f); njInit(); // 初始化nj_context_t if (njDecode(buf, size)) { printf("Error decoding the input file.\n"); return 1; } f = fopen((argc > 2) ? argv[2] : (njIsColor() ? "nanojpeg_out.ppm" : "nanojpeg_out.pgm"), "wb"); if (!f) { printf("Error opening the output file.\n"); return 1; } fprintf(f, "P%d\n%d %d\n255\n", njIsColor() ? 6 : 5, njGetWidth(), njGetHeight()); fwrite(njGetImage(), 1, njGetImageSize(), f); fclose(f); njDone(); return 0; } #endif // 解释什么是stride http://msdn.microsoft.com/en-us/library/windows/desktop/aa473780(v=vs.85).aspx /////////////////////////////////////////////////////////////////////////////// // IMPLEMENTATION SECTION // // you may stop reading here // /////////////////////////////////////////////////////////////////////////////// #ifndef _NJ_INCLUDE_HEADER_ONLY #ifdef _MSC_VER #define NJ_INLINE static __inline #define NJ_FORCE_INLINE static __forceinline #else #define NJ_INLINE static inline #define NJ_FORCE_INLINE static inline #endif #if NJ_USE_LIBC #include <stdlib.h> #include <string.h> #define njAllocMem malloc #define njFreeMem free #define njFillMem memset #define njCopyMem memcpy #elif NJ_USE_WIN32 #include <windows.h> #define njAllocMem(size) ((void*) LocalAlloc(LMEM_FIXED, (SIZE_T)(size))) #define njFreeMem(block) ((void) LocalFree((HLOCAL) block)) NJ_INLINE void njFillMem(void* block, unsigned char value, int count) { __asm { mov edi, block mov al, value mov ecx, count rep stosb } } NJ_INLINE void njCopyMem(void* dest, const void* src, int count) { __asm { mov edi, dest mov esi, src mov ecx, count rep movsb } } #else extern void* njAllocMem(int size); extern void njFreeMem(void* block); extern void njFillMem(void* block, unsigned char byte, int size); extern void njCopyMem(void* dest, const void* src, int size); #endif typedef struct _nj_code { unsigned char bits, code; } nj_vlc_code_t; typedef struct _nj_cmp { int cid; int ssx, ssy; // 水平/垂直因子 int width, height; int stride; int qtsel; // Quantization Table量化表 int actabsel, dctabsel; // AC/DC Huffman Table int dcpred; unsigned char *pixels; } nj_component_t; // 颜色分量 typedef struct _nj_ctx { nj_result_t error; const unsigned char *pos; // 待解码数据指针(按字节来) int size; // 整个数据的长度 int length; // 某一个marker内容的长度 int width, height; // 图片宽和高度 int mbwidth, mbheight; // MCU水平/垂直个数 int mbsizex, mbsizey; // MCU宽/高 int ncomp; // 颜色分量数 nj_component_t comp[3]; // YCbCr int qtused, qtavail; // 这两个目前看不出来很大用处 unsigned char qtab[4][64]; // 但是目前似乎只有2个 nj_vlc_code_t vlctab[4][65536]; // 构造所有16位数的Huffman基数 // 目前基本上是4个(直/交/0/1) int buf, bufbits; // 这是用来做什么的 buf是存放内容的 bufbits是计数器,存放了多少个bits int block[64]; int rstinterval; unsigned char *rgb; // 解析出来的RGB所要占用的内存 // 每1个点包含3个字节,按找RGB的顺序 } nj_context_t; static nj_context_t nj; static const char njZZ[64] = { 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63 }; /* 0 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 */ NJ_FORCE_INLINE unsigned char njClip(const int x) { // 限定范围是0 ~ 255之间 return (x < 0) ? 0 : ((x > 0xFF) ? 0xFF : (unsigned char) x); } #define W1 2841 #define W2 2676 #define W3 2408 #define W5 1609 #define W6 1108 #define W7 565 NJ_INLINE void njRowIDCT(int* blk) { // 按行来操作的 0 ~ 7 // 8 ~ 15 int x0, x1, x2, x3, x4, x5, x6, x7, x8; if (!((x1 = blk[4] << 11) | (x2 = blk[6]) | (x3 = blk[2]) | (x4 = blk[1]) | (x5 = blk[7]) | (x6 = blk[5]) | (x7 = blk[3]))) { blk[0] = blk[1] = blk[2] = blk[3] = blk[4] = blk[5] = blk[6] = blk[7] = blk[0] << 3; return; } x0 = (blk[0] << 11) + 128; x8 = W7 * (x4 + x5); x4 = x8 + (W1 - W7) * x4; x5 = x8 - (W1 + W7) * x5; x8 = W3 * (x6 + x7); x6 = x8 - (W3 - W5) * x6; x7 = x8 - (W3 + W5) * x7; x8 = x0 + x1; x0 -= x1; x1 = W6 * (x3 + x2); x2 = x1 - (W2 + W6) * x2; x3 = x1 + (W2 - W6) * x3; x1 = x4 + x6; x4 -= x6; x6 = x5 + x7; x5 -= x7; x7 = x8 + x3; x8 -= x3; x3 = x0 + x2; x0 -= x2; x2 = (181 * (x4 + x5) + 128) >> 8; x4 = (181 * (x4 - x5) + 128) >> 8; blk[0] = (x7 + x1) >> 8; blk[1] = (x3 + x2) >> 8; blk[2] = (x0 + x4) >> 8; blk[3] = (x8 + x6) >> 8; blk[4] = (x8 - x6) >> 8; blk[5] = (x0 - x4) >> 8; blk[6] = (x3 - x2) >> 8; blk[7] = (x7 - x1) >> 8; } NJ_INLINE void njColIDCT(const int* blk, unsigned char *out, int stride) { int x0, x1, x2, x3, x4, x5, x6, x7, x8; if (!((x1 = blk[8*4] << 8) | (x2 = blk[8*6]) | (x3 = blk[8*2]) | (x4 = blk[8*1]) | (x5 = blk[8*7]) | (x6 = blk[8*5]) | (x7 = blk[8*3]))) { x1 = njClip(((blk[0] + 32) >> 6) + 128); for (x0 = 8; x0; --x0) { *out = (unsigned char) x1; out += stride; } return; } x0 = (blk[0] << 8) + 8192; x8 = W7 * (x4 + x5) + 4; x4 = (x8 + (W1 - W7) * x4) >> 3; x5 = (x8 - (W1 + W7) * x5) >> 3; x8 = W3 * (x6 + x7) + 4; x6 = (x8 - (W3 - W5) * x6) >> 3; x7 = (x8 - (W3 + W5) * x7) >> 3; x8 = x0 + x1; x0 -= x1; x1 = W6 * (x3 + x2) + 4; x2 = (x1 - (W2 + W6) * x2) >> 3; x3 = (x1 + (W2 - W6) * x3) >> 3; x1 = x4 + x6; x4 -= x6; x6 = x5 + x7; x5 -= x7; x7 = x8 + x3; x8 -= x3; x3 = x0 + x2; x0 -= x2; x2 = (181 * (x4 + x5) + 128) >> 8; // Y,Cb和Cr的值都范围都是-128 ~ 127,并且在FDCT的时候有先减去128,所以现在要IDCT之后再加上128 x4 = (181 * (x4 - x5) + 128) >> 8; *out = njClip(((x7 + x1) >> 14) + 128); out += stride; *out = njClip(((x3 + x2) >> 14) + 128); out += stride; *out = njClip(((x0 + x4) >> 14) + 128); out += stride; *out = njClip(((x8 + x6) >> 14) + 128); out += stride; *out = njClip(((x8 - x6) >> 14) + 128); out += stride; *out = njClip(((x0 - x4) >> 14) + 128); out += stride; *out = njClip(((x3 - x2) >> 14) + 128); out += stride; *out = njClip(((x7 - x1) >> 14) + 128); } #define njThrow(e) do { nj.error = e; return; } while (0) #define njCheckError() do { if (nj.error) return; } while (0) static int njShowBits(int bits) { // 能放得下大于32位的值么? unsigned char newbyte; if (!bits) return 0; while (nj.bufbits < bits) { // 也就是说要buf的位数小于已经buf的位数的时候,就直接读出来? if (nj.size <= 0) { nj.buf = (nj.buf << 8) | 0xFF; nj.bufbits += 8; continue; } newbyte = *nj.pos++; // 数据指针是按字节 nj.size--; nj.bufbits += 8; nj.buf = (nj.buf << 8) | newbyte; // 高位最终会被覆盖掉,比如我要buf一个64位的值怎么办? if (newbyte == 0xFF) { if (nj.size) { unsigned char marker = *nj.pos++; nj.size--; switch (marker) { case 0x00: case 0xFF: break; case 0xD9: nj.size = 0; break; default: if ((marker & 0xF8) != 0xD0) nj.error = NJ_SYNTAX_ERROR; else { nj.buf = (nj.buf << 8) | marker; nj.bufbits += 8; } } } else nj.error = NJ_SYNTAX_ERROR; } } return (nj.buf >> (nj.bufbits - bits)) & ((1 << bits) - 1); } NJ_INLINE void njSkipBits(int bits) { if (nj.bufbits < bits) (void) njShowBits(bits); nj.bufbits -= bits; } NJ_INLINE int njGetBits(int bits) { int res = njShowBits(bits); njSkipBits(bits); return res; } NJ_INLINE void njByteAlign(void) { nj.bufbits &= 0xF8; // (1111 1000)8的倍数,不满8的部分丢弃 } static void njSkip(int count) { nj.pos += count; // 数据指针增加 nj.size -= count; // 总体数据大小减去count nj.length -= count; // 当前marker长度减去count if (nj.size < 0) nj.error = NJ_SYNTAX_ERROR; } NJ_INLINE unsigned short njDecode16(const unsigned char *pos) { return (pos[0] << 8) | pos[1]; // 00000000 00001101 } static void njDecodeLength(void) { // decode长度字段,这个方法调用一般都是已经进入到特定的marker之后 if (nj.size < 2) njThrow(NJ_SYNTAX_ERROR); nj.length = njDecode16(nj.pos); // 该marker的长度(除去marker名字所占用的2个字节) if (nj.length > nj.size) njThrow(NJ_SYNTAX_ERROR); njSkip(2); } NJ_INLINE void njSkipMarker(void) { njDecodeLength(); njSkip(nj.length); } NJ_INLINE void njDecodeSOF(void) { // 解析Start of Frame的时候就会把所需要的内存都分配好 int i, ssxmax = 0, ssymax = 0; nj_component_t* c; njDecodeLength(); // 解析长度并移动数据指针 if (nj.length < 9) njThrow(NJ_SYNTAX_ERROR); if (nj.pos[0] != 8) njThrow(NJ_UNSUPPORTED); // 样本精度,一般都是8 nj.height = njDecode16(nj.pos + 1); // 图片高度/宽度 nj.width = njDecode16(nj.pos + 3); nj.ncomp = nj.pos[5]; // 颜色分量数据,一般都是3 njSkip(6); // 之前共6个字节数据,所以移动数据指针6个字节 switch (nj.ncomp) { // 目前只支持1和3这两种 case 1: case 3: break; default: njThrow(NJ_UNSUPPORTED); } if (nj.length < (nj.ncomp * 3)) njThrow(NJ_SYNTAX_ERROR); // 数据量肯定是要大于颜色分量数 multiply 3,因为接着存颜色分量信息的每个结构占3个字节 // 颜色分量ID占用1个字节,水平/垂直因子占用1个字节(高4位水平,低4位垂直),量化表占用1个字节 for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) { c->cid = nj.pos[0]; // 颜色分量ID if (!(c->ssx = nj.pos[1] >> 4)) njThrow(NJ_SYNTAX_ERROR); // 高4位(水平因子) if (c->ssx & (c->ssx - 1)) njThrow(NJ_UNSUPPORTED); // non-power of two if (!(c->ssy = nj.pos[1] & 15)) njThrow(NJ_SYNTAX_ERROR); // (00001111)低4位(垂直因子) if (c->ssy & (c->ssy - 1)) njThrow(NJ_UNSUPPORTED); // non-power of two if ((c->qtsel = nj.pos[2]) & 0xFC) njThrow(NJ_SYNTAX_ERROR); // (11111101) 这里0xFC是用在这里干什么的? njSkip(3); // 移动数据指针到下一个颜色分量 nj.qtused |= 1 << c->qtsel; // 这里是做什么用的?看不出来 if (c->ssx > ssxmax) ssxmax = c->ssx; // 记录最大水平因子 if (c->ssy > ssymax) ssymax = c->ssy; // 记录最大垂直因子 } if (nj.ncomp == 1) { // 只有一种颜色分量的时候就简单啦 c = nj.comp; c->ssx = c->ssy = ssxmax = ssymax = 1; } nj.mbsizex = ssxmax << 3; // MCU宽 是 水平采样因子最大值 multiply 8 nj.mbsizey = ssymax << 3; // MCU高 是 垂直采样因子最大值 multiply 8 nj.mbwidth = (nj.width + nj.mbsizex - 1) / nj.mbsizex; // 分子采用+ nj.mbsizex - 1就取到大于但是最接近(等于)宽度的值, // 并且这个值是MCU宽度整数倍 // 这里是水平方向MCU的个数 nj.mbheight = (nj.height + nj.mbsizey - 1) / nj.mbsizey; // 这里是垂直方向MCU的个数 for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) { c->width = (nj.width * c->ssx + ssxmax - 1) / ssxmax; // 采样宽度? 最大水平/垂直因子的值就是图片原来的值,否则就会根据因子做相应的减少 c->stride = (c->width + 7) & 0x7FFFFFF8; // (0111 1111 1111 1111 1111 1111 1111 1000) 做什么?以1234567结尾的都省略掉? // 变成8的整数 // 补齐8位,注意前面有加7,所以总是不会比原来的少,比如原来是227,那么这里就会变成232 // 这是按照数据单元计算的,所以不对 printf("%d, stride %d\n", i, c->stride); c->height = (nj.height * c->ssy + ssymax - 1) / ssymax; c->stride = nj.mbwidth * nj.mbsizex * c->ssx / ssxmax; // 再计算一遍stride有什么用?前面计算的是错误的,没有考虑MCU宽度 // 这里都已经是round过的了,所以直接计算 printf("%d, stride again %d\n", i, c->stride); if (((c->width < 3) && (c->ssx != ssxmax)) || ((c->height < 3) && (c->ssy != ssymax))) njThrow(NJ_UNSUPPORTED); if (!(c->pixels = njAllocMem(c->stride * (nj.mbheight * nj.mbsizey * c->ssy / ssymax)))) njThrow(NJ_OUT_OF_MEM); // 为分量分配内存 // 大小是所有MCU的 // 可能比图片实际 // 尺寸大 } if (nj.ncomp == 3) { // 只有有3个颜色分量的时候才需要 nj.rgb = njAllocMem(nj.width * nj.height * nj.ncomp); if (!nj.rgb) njThrow(NJ_OUT_OF_MEM); } njSkip(nj.length); } NJ_INLINE void njDecodeDHT(void) { int codelen, currcnt, remain, spread, i, j; nj_vlc_code_t *vlc; static unsigned char counts[16]; // 码字 njDecodeLength(); while (nj.length >= 17) { // 码字的数量(16) + 类型和ID(1) i = nj.pos[0]; // 类型和ID if (i & 0xEC) njThrow(NJ_SYNTAX_ERROR); // (11101100) if (i & 0x02) njThrow(NJ_UNSUPPORTED); // (00000010) i = (i | (i >> 3)) & 3; // combined DC/AC + tableid value // 直流0,直流1,交流0,交流1 for (codelen = 1; codelen <= 16; ++codelen) // 码字长度 counts[codelen - 1] = nj.pos[codelen]; // 读取码字 njSkip(17); vlc = &nj.vlctab[i][0]; remain = spread = 65536; for (codelen = 1; codelen <= 16; ++codelen) { spread >>= 1; // 干什么? currcnt = counts[codelen - 1]; if (!currcnt) continue; // 如果该位数没有码字 if (nj.length < currcnt) njThrow(NJ_SYNTAX_ERROR); remain -= currcnt << (16 - codelen); if (remain < 0) njThrow(NJ_SYNTAX_ERROR); for (i = 0; i < currcnt; ++i) { // 码字个数,同样位数的码字可以有多个 register unsigned char code = nj.pos[i]; for (j = spread; j; --j) { // 保存这么多个有什么作用? vlc->bits = (unsigned char) codelen; // 码字位数 vlc->code = code; // 码字值 ++vlc; } } njSkip(currcnt); } while (remain--) { vlc->bits = 0; ++vlc; } } if (nj.length) njThrow(NJ_SYNTAX_ERROR); } NJ_INLINE void njDecodeDQT(void) { int i; unsigned char *t; njDecodeLength(); while (nj.length >= 65) { i = nj.pos[0]; // QT信息,高4位为QT精度,低4位为QT号 if (i & 0xFC) njThrow(NJ_SYNTAX_ERROR); // (1111 1110)这个用来检测QT号码是否正确的吗?目前精度好像都为0,所以这么写? nj.qtavail |= 1 << i; // XXX 直接通过这里转换为数量? t = &nj.qtab[i][0]; for (i = 0; i < 64; ++i) t[i] = nj.pos[i + 1]; // 读取到QT数组当中,但应该还是按照文件流当中的排列 njSkip(65); } if (nj.length) njThrow(NJ_SYNTAX_ERROR); } NJ_INLINE void njDecodeDRI(void) { njDecodeLength(); if (nj.length < 2) njThrow(NJ_SYNTAX_ERROR); nj.rstinterval = njDecode16(nj.pos); njSkip(nj.length); } static int njGetVLC(nj_vlc_code_t* vlc, unsigned char* code) { // Variable Length Coding int value = njShowBits(16); int bits = vlc[value].bits; if (!bits) { nj.error = NJ_SYNTAX_ERROR; return 0; } njSkipBits(bits); value = vlc[value].code; if (code) *code = (unsigned char) value; bits = value & 15; if (!bits) return 0; value = njGetBits(bits); if (value < (1 << (bits - 1))) value += ((-1) << bits) + 1; return value; } NJ_INLINE void njDecodeBlock(nj_component_t* c, unsigned char* out) { unsigned char code = 0; int value, coef = 0; njFillMem(nj.block, 0, sizeof(nj.block)); c->dcpred += njGetVLC(&nj.vlctab[c->dctabsel][0], NULL); // DC 0/1 不会和AC重复 nj.block[0] = (c->dcpred) * nj.qtab[c->qtsel][0]; // DC // 这里是反量化? do { value = njGetVLC(&nj.vlctab[c->actabsel][0], &code); // DC 2/3 if (!code) break; // EOB if (!(code & 0x0F) && (code != 0xF0)) njThrow(NJ_SYNTAX_ERROR); coef += (code >> 4) + 1; // coefficient 系数 if (coef > 63) njThrow(NJ_SYNTAX_ERROR); nj.block[(int) njZZ[coef]] = value * nj.qtab[c->qtsel][coef]; // AC 这里是反量化? } while (coef < 63); for (coef = 0; coef < 64; coef += 8) njRowIDCT(&nj.block[coef]); // 上面先Huffman解码/反量化,这里行(反DCT) for (coef = 0; coef < 8; ++coef) njColIDCT(&nj.block[coef], &out[coef], c->stride); } NJ_INLINE void njDecodeScan(void) { int i, mbx, mby, sbx, sby; int rstcount = nj.rstinterval, nextrst = 0; nj_component_t* c; njDecodeLength(); if (nj.length < (4 + 2 * nj.ncomp)) njThrow(NJ_SYNTAX_ERROR); if (nj.pos[0] != nj.ncomp) njThrow(NJ_UNSUPPORTED); njSkip(1); // 颜色分量数量 for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) { if (nj.pos[0] != c->cid) njThrow(NJ_SYNTAX_ERROR); // 颜色分量ID if (nj.pos[1] & 0xEE) njThrow(NJ_SYNTAX_ERROR); c->dctabsel = nj.pos[1] >> 4; // 高4位为直流表DC Table c->actabsel = (nj.pos[1] & 1) | 2; // 低4位为交流表AC Table(这里有做特殊处理,所以AC的表名不会和DC相同) printf("DC/AC Huffman table ids: %d/%d\n", c->dctabsel, c->actabsel); njSkip(2); } if (nj.pos[0] || (nj.pos[1] != 63) || nj.pos[2]) njThrow(NJ_UNSUPPORTED); njSkip(nj.length); // 忽略3个字节 通常为 00 3F 00 // 2 + 1 + 6 + 3为12字节,这个marker的长度刚好为12字节 // 接下来都是编码过的图像数据 for (mbx = mby = 0;;) { for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) // 每个分量都要decode for (sby = 0; sby < c->ssy; ++sby) // 水平/垂直因子 for (sbx = 0; sbx < c->ssx; ++sbx) { njDecodeBlock(c, &c->pixels[((mby * c->ssy + sby) * c->stride + mbx * c->ssx + sbx) << 3]); // 读取原始编码过 // 的图片数据到block中 // 并反量化,反离散余弦变换 njCheckError(); } if (++mbx >= nj.mbwidth) { // 读完所有的MCU,到达最右就返回从下一行开始 mbx = 0; if (++mby >= nj.mbheight) break; // 到达最底行的时候推出,decode结束 } if (nj.rstinterval && !(--rstcount)) { // restart marker njByteAlign(); i = njGetBits(16); if (((i & 0xFFF8) != 0xFFD0) || ((i & 7) != nextrst)) njThrow(NJ_SYNTAX_ERROR); nextrst = (nextrst + 1) & 7; rstcount = nj.rstinterval; for (i = 0; i < 3; ++i) nj.comp[i].dcpred = 0; } } nj.error = __NJ_FINISHED; } #if NJ_CHROMA_FILTER #define CF4A (-9) #define CF4B (111) #define CF4C (29) #define CF4D (-3) #define CF3A (28) #define CF3B (109) #define CF3C (-9) #define CF3X (104) #define CF3Y (27) #define CF3Z (-3) #define CF2A (139) #define CF2B (-11) #define CF(x) njClip(((x) + 64) >> 7) // 通常我们放大图片的时候就需要upsampling,缩小的时候就downsampling,通称为resampling // 这里Cb/Cr分量的会少些,所以需要upsampling NJ_INLINE void njUpsampleH(nj_component_t* c) { printf("njUpsampleH %d\n", c->cid); const int xmax = c->width - 3; unsigned char *out, *lin, *lout; int x, y; out = njAllocMem((c->width * c->height) << 1); if (!out) njThrow(NJ_OUT_OF_MEM); lin = c->pixels; lout = out; for (y = c->height; y; --y) { lout[0] = CF(CF2A * lin[0] + CF2B * lin[1]); lout[1] = CF(CF3X * lin[0] + CF3Y * lin[1] + CF3Z * lin[2]); lout[2] = CF(CF3A * lin[0] + CF3B * lin[1] + CF3C * lin[2]); for (x = 0; x < xmax; ++x) { lout[(x << 1) + 3] = CF(CF4A * lin[x] + CF4B * lin[x + 1] + CF4C * lin[x + 2] + CF4D * lin[x + 3]); lout[(x << 1) + 4] = CF(CF4D * lin[x] + CF4C * lin[x + 1] + CF4B * lin[x + 2] + CF4A * lin[x + 3]); } lin += c->stride; lout += c->width << 1; lout[-3] = CF(CF3A * lin[-1] + CF3B * lin[-2] + CF3C * lin[-3]); lout[-2] = CF(CF3X * lin[-1] + CF3Y * lin[-2] + CF3Z * lin[-3]); lout[-1] = CF(CF2A * lin[-1] + CF2B * lin[-2]); } c->width <<= 1; c->stride = c->width; njFreeMem(c->pixels); c->pixels = out; } NJ_INLINE void njUpsampleV(nj_component_t* c) { printf("njUpsampleV %d\n", c->cid); const int w = c->width, s1 = c->stride, s2 = s1 + s1; unsigned char *out, *cin, *cout; int x, y; out = njAllocMem((c->width * c->height) << 1); if (!out) njThrow(NJ_OUT_OF_MEM); for (x = 0; x < w; ++x) { cin = &c->pixels[x]; cout = &out[x]; *cout = CF(CF2A * cin[0] + CF2B * cin[s1]); cout += w; *cout = CF(CF3X * cin[0] + CF3Y * cin[s1] + CF3Z * cin[s2]); cout += w; *cout = CF(CF3A * cin[0] + CF3B * cin[s1] + CF3C * cin[s2]); cout += w; cin += s1; for (y = c->height - 3; y; --y) { *cout = CF(CF4A * cin[-s1] + CF4B * cin[0] + CF4C * cin[s1] + CF4D * cin[s2]); cout += w; *cout = CF(CF4D * cin[-s1] + CF4C * cin[0] + CF4B * cin[s1] + CF4A * cin[s2]); cout += w; cin += s1; } cin += s1; *cout = CF(CF3A * cin[0] + CF3B * cin[-s1] + CF3C * cin[-s2]); cout += w; *cout = CF(CF3X * cin[0] + CF3Y * cin[-s1] + CF3Z * cin[-s2]); cout += w; *cout = CF(CF2A * cin[0] + CF2B * cin[-s1]); } c->height <<= 1; c->stride = c->width; njFreeMem(c->pixels); c->pixels = out; } #else NJ_INLINE void njUpsample(nj_component_t* c) { printf("njUpsample %d\n", c->cid); int x, y, xshift = 0, yshift = 0; unsigned char *out, *lin, *lout; while (c->width < nj.width) { c->width <<= 1; ++xshift; } while (c->height < nj.height) { c->height <<= 1; ++yshift; } out = njAllocMem(c->width * c->height); // 放大后的尺寸 if (!out) njThrow(NJ_OUT_OF_MEM); lin = c->pixels; lout = out; for (y = 0; y < c->height; ++y) { lin = &c->pixels[(y >> yshift) * c->stride]; for (x = 0; x < c->width; ++x) lout[x] = lin[x >> xshift]; lout += c->width; } c->stride = c->width; njFreeMem(c->pixels); c->pixels = out; } #endif NJ_INLINE void njConvert() { int i; nj_component_t* c; for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) { // 如果需要的话就upsampling #if NJ_CHROMA_FILTER while ((c->width < nj.width) || (c->height < nj.height)) { if (c->width < nj.width) njUpsampleH(c); njCheckError(); if (c->height < nj.height) njUpsampleV(c); njCheckError(); } #else if ((c->width < nj.width) || (c->height < nj.height)) njUpsample(c); #endif if ((c->width < nj.width) || (c->height < nj.height)) njThrow(NJ_INTERNAL_ERR); } if (nj.ncomp == 3) { // SEE njGetImage() // convert to RGB int x, yy; unsigned char *prgb = nj.rgb; const unsigned char *py = nj.comp[0].pixels; const unsigned char *pcb = nj.comp[1].pixels; const unsigned char *pcr = nj.comp[2].pixels; // 多余的数据(编/解码是对齐用的)会被丢弃吗? for (yy = nj.height; yy; --yy) { // 列 for (x = 0; x < nj.width; ++x) { // 行 register int y = py[x] << 8; // 这是为什么? 色彩空间转换公式计算需要 register int cb = pcb[x] - 128; // YCbCr的Cb和Cr一般都是有符号数,但是在JPEG当中都是无符号数 register int cr = pcr[x] - 128; *prgb++ = njClip((y + 359 * cr + 128) >> 8); // 色彩空间转换,YCbCr到RGB *prgb++ = njClip((y - 88 * cb - 183 * cr + 128) >> 8); *prgb++ = njClip((y + 454 * cb + 128) >> 8); } py += nj.comp[0].stride; // 移动YCbCr数据指针,每一行都是有stride的,所以当需要的数据都得到时,后面的就不管,直接丢弃,移动到下一行 pcb += nj.comp[1].stride; pcr += nj.comp[2].stride; } } else if (nj.comp[0].width != nj.comp[0].stride) { // 如果宽度和stride都一样,什么都不用做 // grayscale -> only remove stride unsigned char *pin = &nj.comp[0].pixels[nj.comp[0].stride]; unsigned char *pout = &nj.comp[0].pixels[nj.comp[0].width]; int y; for (y = nj.comp[0].height - 1; y; --y) { njCopyMem(pout, pin, nj.comp[0].width); pin += nj.comp[0].stride; pout += nj.comp[0].width; } nj.comp[0].stride = nj.comp[0].width; } } void njInit(void) { njFillMem(&nj, 0, sizeof(nj_context_t)); // 初始化nj_context_t } void njDone(void) { int i; for (i = 0; i < 3; ++i) if (nj.comp[i].pixels) njFreeMem((void*) nj.comp[i].pixels); if (nj.rgb) njFreeMem((void*) nj.rgb); njInit(); } nj_result_t njDecode(const void* jpeg, const int size) { njDone(); nj.pos = (const unsigned char*) jpeg; nj.size = size & 0x7FFFFFFF; // ? if (nj.size < 2) return NJ_NO_JPEG; if ((nj.pos[0] ^ 0xFF) | (nj.pos[1] ^ 0xD8)) return NJ_NO_JPEG; // 不以0xFFD8打头(为什么要用异或来判断?) njSkip(2); while (!nj.error) { // 有“错误”的时候离开 if ((nj.size < 2) || (nj.pos[0] != 0xFF)) return NJ_SYNTAX_ERROR; // 太小,或者不以0xFF打头 njSkip(2); // 移动到标签的后面(长度字段的前面) switch (nj.pos[-1]) { case 0xC0: njDecodeSOF(); break; case 0xC4: njDecodeDHT(); break; case 0xDB: njDecodeDQT(); break; case 0xDD: njDecodeDRI(); break; case 0xDA: njDecodeScan(); break; case 0xFE: njSkipMarker(); break; default: if ((nj.pos[-1] & 0xF0) == 0xE0) // JPG0和APP0字段,目前都忽略 njSkipMarker(); else return NJ_UNSUPPORTED; } } if (nj.error != __NJ_FINISHED) return nj.error; nj.error = NJ_OK; njConvert(); return nj.error; } int njGetWidth(void) { return nj.width; } int njGetHeight(void) { return nj.height; } int njIsColor(void) { return (nj.ncomp != 1); } unsigned char* njGetImage(void) { return (nj.ncomp == 1) ? nj.comp[0].pixels : nj.rgb; } // 一/三个分量 int njGetImageSize(void) { return nj.width * nj.height * nj.ncomp; } #endif // _NJ_INCLUDE_HEADER_ONLY
请问需要怎样设定输入数据开始调试?
你想什么样方式的调试?调试什么?
我是在visual studio环境下运行,但是显示一个问题,说没有程序入口,而且这个代码是实现jpeg解码功能,我们在调试的时候应该将待解码数据流放在什么位置呢,还有其他的需要一些什么设置呢?
它默认编译是没有main函数的,它的main函数是通过宏_NJ_EXAMPLE_PROGRAM包起来的,所以你编译的时候要启用这个宏,在GCC下通过-D_NJ_EXAMPLE_PROGRAM就可以启用这个宏,所以在VS下你也需要启用这个宏,这样main函数才会参与编译。
运行的时候,这个程序是接受输入参数的,比如我这里./nanojpeg testorig.jpg就有传递一个待解码的图片路径,如果解码成功就会在程序目录下生成一张PPM的图片,也就是你解码出来的结果。
请问这个代码运行完毕后可以得到什么结果,如何查看
在上面评论出回答了,建议你多读读http://keyj.emphy.de/nanojpeg/主页,代码里面的注释,还有这篇博客上面编译运行的命令。
今天仔细研究了代码,终于弄明白整个过程了,多谢了