I believe that users who have used libjpeg should like libjpeg, which is easy to use and can be controlled at Will with good stability. However, the libjpeg library provided by the official website,
File is required for both compression and decompression, so that we need to implement the corresponding structure if we want to directly compress or decompress the image in the memory,
In short, it is troublesome, especially for beginners, and I do not know where to start. Fortunately, libjpeg provides us with the source code. Today I will introduce you to how to modify the source code,
Libjpeg can easily process images in the memory without file operations.
1. Create your own libjpeg Project
To facilitate the compilation after modification, and to make it easy to use the libjpeg library in the VC environment, follow these steps to convert libjpeg to a project in the VC environment.
1. Create an empty static library project in the VC environment. The project name is libjpeg. Note that the new project should not contain MFC or pre-compile the header file;
2. Set jcapimin. c jcapistd. c jccoefct. c jccolor. c jcdctmgr. c jchuff. c Under libjpeg.
Jcinit. c jcmainct. c jcmarker. c jcmaster. c jcomapi. c jcparam. c
Jcphuff. c jcprepct. c jcsample. c jctrans. c jdapimin. c jdapistd. c
Jdatadst. c jdatasrc. c jdcoefct. c jdcolor. c jddctmgr. c jdhuff. c
Jdinput. c jdmainct. c jdmarker. c jdmaster. c jdmerge. c jdphuff. c
Jdpostct. c jdsample. c jdtrans. c jerror. c jfdctflt. c jfdctfst. c
Jfdctint. c jidctflt. c jidctfst. c jidctint. c jidctred. c jquant1.c
Jquant2.c jutils. c jmemmgr. c
Jchuff. h jconfig. h jdhuff. h jdct. h jerror. h jinclude. h jmemsys. h jmorecfg. h
Jpegint. H. jpeglib. h jversion. h and other files are copied to the folder of the new project, and the. c file is renamed as. cpp;
3. Add all source files and header files to the new project;
4. Compile the new project and generate libjpeg. Lib.
2. Analyze and modify the source code
We know that libjpeg uses file to access image data. Next, we will analyze how libjpeg uses file to access image data,
Then we replace all the file operations (I/O) with the memory copy method, which achieves the goal of compressing and extracting images in the memory.
Next, we will analyze how libjpeg uses file to store data when compressing an image. Let's first look at the functions that we call related to files during image compression:
Pai_stdio_dest (j_compres_ptr Cinfo, file * OUTFILE );
We can find the source code of this function (jdatadst. cpp file 130th ):
1 Global (void)
2 1__stdio_dest (j_compress_ptr Cinfo, file * OUTFILE)
3 {
4 my_dest_ptr DEST;
5/* the destination object is made permanent so that multiple JPEG images
6 * can be written to the same file without re-executing into _stdio_dest.
7 * This makes it dangerous to use this manager and a different destination
8 * manager serially with the same JPEG object, because their private object
9 * sizes may be different. Caveat programmer.
10 */
11 if (Cinfo-> DEST = NULL) {/* First time for this jpeg object? */
12 Cinfo-> DEST = (struct cmd_destination_mgr *)
13 (* Cinfo-> mem-> alloc_small) (j_common_ptr) Cinfo, jpool_permanent,
14 sizeof (my_destination_mgr ));
15}
16 DEST = (my_dest_ptr) Cinfo-> DEST;
17 DEST-> pub. init_destination = init_destination;
18 DEST-> pub. empty_output_buffer = empty_output_buffer;
19 DEST-> pub. term_destination = term_destination;
20 DEST-> OUTFILE = OUTFILE;
21}
As you can see in Row 3, the function assigns a pointer of the file type to DEST-> OUTFILE. Obviously, subsequent operations on the file are switched to DEST-> OUTFILE operations,
As long as we find all the functions that reference OUTFILE, we can know how libjpeg compresses the image to a file. Therefore, we continue to search for OUTFILE. The search result is as follows:
Find all "OUTFILE", subfolders, find results 1, "entire solution"
E:/vs2005/libjpeg/jpeglib. H (910): extern (void) 1__stdio_dest JPP (j_compress_ptr Cinfo, file * OUTFILE ));
E:/vs2005/libjpeg/jdatadst. cpp (28): file * OUTFILE;/* Target stream */
E:/vs2005/libjpeg/jdatadst. cpp (85): If (jfwrite (DEST-> OUTFILE, DEST-> buffer, output_buf_size )! =
E:/vs2005/libjpeg/jdatadst. cpp (113): If (jfwrite (DEST-> OUTFILE, DEST-> buffer, datacount )! = Datacount)
E:/vs2005/libjpeg/jdatadst. cpp (116): fflush (DEST-> OUTFILE );
E:/vs2005/libjpeg/jdatadst. cpp (118): If (ferror (DEST-> OUTFILE ))
E:/vs2005/libjpeg/jdatadst. cpp (130): pai_stdio_dest (j_compress_ptr Cinfo, file * OUTFILE)
E:/vs2005/libjpeg/jdatadst. cpp (150): DEST-> OUTFILE = OUTFILE;
Matching lines: 8 matching files: 2 Total files searched: 57
As you can see, there are eight OUTFILE variables referenced. The first is the function declaration, the second is the variable declaration, and the third, fourth, fifth, and sixth are file operations, part 7 and Part 8 we
We have already seen it. We only need to modify these eight points to achieve our goal. As follows:
Extern (void) 1__stdio_dest JPP (j_compress_ptr Cinfo, char * outdata); // By extern (void) 1__stdio_dest JPP
(J_compress_ptr Cinfo, file * OUTFILE); rewrite
Char * outdata;/* Target stream * // changed by file * OUTFILE;/* Target stream */
Jdatadst. cpp file row 87th empty_output_buffer (j_compress_ptr Cinfo) Function
Memcpy (DEST-> outdata, DEST-> buffer, output_buf_size); // rewrite by jfwrite (DEST-> OUTFILE, DEST-> buffer, output_buf_size)
Jdatadst. cpp file row 114th term_destination (j_compress_ptr Cinfo)
Memcpy (DEST-> outdata, DEST-> buffer, datacount); // rewrite by jfwrite (DEST-> OUTFILE, DEST-> buffer, datacount)
Delete fflush (DEST-> OUTFILE); and if (ferror (DEST-> OUTFILE) and other related statements.
Peg_stdio_dest (j_compress_ptr Cinfo, char * outdata) // rewrite by peg_stdio_dest (j_compress_ptr Cinfo, file * OUTFILE)
DeST-> outdata = outdata; // modified by DEST-> OUTFILE = OUTFILE;
We can compile it here to avoid errors, but do you think there is a problem? Yes, we found that we did not provide an offset for the memory area (
After each append image data, the offset points to the current position ),
In addition, since the data size after image compression can be known only after compression, we also need a variable to indicate the image data size.
We add these two variables to the end of outdata, just like outdata, As a member variable of DeST, as follows:
Typedef struct {
Struct cmd_destination_mgr pub;/* public fields */
Char * outdata;/* Target stream */
Int * psize; // the newly added variable. The pointer is provided by the caller. The image size is returned after compression.
Int noutoffset; // new variable
Joctet * buffer;/* Start of buffer */
} My_destination_mgr;
We will provide the psize pointer through the pai_stdio_dest function, and initialize the newly added variables in the implementation function of pai_stdio_dest, as shown below:
Global (void)
Pai_stdio_dest (j_compress_ptr Cinfo, char * outdata, int * psize)
{
My_dest_ptr DEST;
/* The destination object is made permanent so that multiple JPEG images
* Can be written to the same file without re-executing into _stdio_dest.
* This makes it dangerous to use this manager and a different destination
* Manager serially with the same JPEG object, because their private object
* Sizes may be different. Caveat programmer.
*/
If (Cinfo-> DEST = NULL) {/* First time for this jpeg object? */
Cinfo-> DEST = (struct cmd_destination_mgr *)
(* Cinfo-> mem-> alloc_small) (j_common_ptr) Cinfo, jpool_permanent,
Sizeof (my_destination_mgr ));
}
DeST = (my_dest_ptr) Cinfo-> DEST;
DeST-> pub. init_destination = init_destination;
DeST-> pub. empty_output_buffer = empty_output_buffer;
DeST-> pub. term_destination = term_destination;
/* Modified code */
DeST-> outdata = outdata;
DeST-> noutoffset = 0;
DeST-> psize = psize;
* (DEST-> psize) = 0;
}
Rewrite Declaration Function
Extern (void) 1__stdio_dest JPP (j_compress_ptr Cinfo, char * outdata, int * psize ));
Jdatadst. cpp file row 87th empty_output_buffer (j_compress_ptr Cinfo) Function
Memcpy (DEST-> outdata + DEST-> noutoffset, DEST-> buffer, output_buf_size); // By jfwrite (DEST-> OUTFILE, DEST-> buffer,
Output_buf_size) rewrite
DeST-> noutoffset + = output_buf_size;
* (DEST-> psize) = DEST-> noutoffset;
Jdatadst. cpp file row 114th term_destination (j_compress_ptr Cinfo)
Memcpy (DEST-> outdata + DEST-> noutoffset, DEST-> buffer, datacount); // rewrite by jfwrite (DEST-> OUTFILE, DEST-> buffer, datacount)
DeST-> noutoffset + = datacount;
* (DEST-> psize) = DEST-> noutoffset;
Re-compile the project so that we can compress the BMP bitmap to the memory. Of course, before calling jpeg_stdio_dest, We need to allocate enough memory and pass the memory pointer.
To the performance_stdio_dest function,
Now, let's analyze how libjpeg reads image data from JPG files when extracting JPG images.
Let's first look at the functions related to file operations that we call when extracting images, as shown below:
Performance_stdio_src (j_decompress_ptr Cinfo, file * infile ).
The my_src_ptr structure is found in the implementation code of this function. In addition, we find that the member variable of this structure related to file operations is infile. Refer to the above content to search for infile.
The search result is as follows:
Find all "infile", subfolders, find results 1, "entire solution"
E:/vs2005/libjpeg/jpeglib. H (911): extern (void) 1__stdio_src JPP (j_decompress_ptr Cinfo, file * infile ));
E:/vs2005/libjpeg/jdatasrc. cpp (28): file * infile;/* Source stream */
E:/vs2005/libjpeg/jdatasrc. cpp (95): nbytes = jfread (SRC-> infile, Src-> buffer, input_buf_size );
E:/vs2005/libjpeg/jdatasrc. cpp (182): __stdio_src (j_decompress_ptr Cinfo, file * infile)
E:/vs2005/libjpeg/jdatasrc. cpp (209): Src-> infile = infile;
Matching lines: 5 matching files: 2 Total files searched: 57
Based on the above experience, we consider that in addition to changing the file * type variable to the char * type variable, we also need to add two variables, the variable of the image size and the image offset, which follow the image
The compression process is similar. The difference is that,
When the image is compressed, the image size is returned by the libjpeg library. Therefore, a pointer is provided to the libjpeg library during the call. during decompression, the image data size is obtained by the caller through a variable (
Is not a pointer) provided to the libjpeg library.
Since I have explained in detail what we have done during image compression, I think readers can easily understand the changes made during decompression. Below I will only list the code we have rewritten.
I will not explain it in detail.
Rows Lib. h 911st
Extern (void) 1__stdio_src JPP (j_decompress_ptr Cinfo, char * indata, int nsize ));
Jdatasrc. cpp row 33rd
/* Expanded data source object for stdio input */
Typedef struct {
Struct cmd_source_mgr pub;/* public fields */
Char * indata;/* Source stream */
Int ninoffset;
Int nsize;
Joctet * buffer;/* Start of buffer */
Boolean start_of_file;/* have we gotten any data yet? */
} My_source_mgr;
Jdatasrc. cpp row 183rd
Global (void)
Performance_stdio_src (j_decompress_ptr Cinfo, char * indata, int nsize)
{
My_src_ptr SRC;
/* The source object and input buffer are made permanent so that a series
* Of JPEG images can be read from the same file by calling pai_stdio_src
* Only before the first one. (If we discarded the buffer at the end
* One image, we 'd likely lose the start of the next one .)
* This makes it unsafe to use this manager and a different source
* Manager serially with the same JPEG object. Caveat programmer.
*/
If (Cinfo-> src = NULL) {/* First time for this jpeg object? */
Cinfo-> src = (struct cmd_source_mgr *)
(* Cinfo-> mem-> alloc_small) (j_common_ptr) Cinfo, jpool_permanent,
Sizeof (my_source_mgr ));
Src = (my_src_ptr) Cinfo-> SRC;
Src-> buffer = (joctet *)
(* Cinfo-> mem-> alloc_small) (j_common_ptr) Cinfo, jpool_permanent,
Input_buf_size * sizeof (joctet ));
}
Src = (my_src_ptr) Cinfo-> SRC;
Src-> pub. init_source = init_source;
Src-> pub. fill_input_buffer = fill_input_buffer;
Src-> pub. skip_input_data = skip_input_data;
Src-> pub. resync_to_restart = pai_resync_to_restart;/* use default method */
Src-> pub. term_source = term_source;
Src-> indata = indata; // Add a row
Src-> nsize = nsize; // newly added
Src-> ninoffset = 0; // newly added
Src-> pub. bytes_in_buffer = 0;/* forces fill_input_buffer on first read */
Src-> pub. next_input_byte = NULL;/* Until buffer loaded */
}
Jdatasrc. cpp row 91st
Methoddef (Boolean)
Fill_input_buffer (j_decompress_ptr Cinfo)
{
My_src_ptr src = (my_src_ptr) Cinfo-> SRC;
Size_t nbytes;
// Nbytes = jfread (SRC-> infile, Src-> buffer, input_buf_size );
Nbytes = Src-> nsize-Src-> ninoffset;
If (nbytes> input_buf_size) nbytes = input_buf_size;
If (nbytes <= 0 ){
If (SRC-> start_of_file)/* treat empty input file as fatal error */
Errexit (Cinfo, jerr_input_empty );
Warnms (Cinfo, jwrn_1__eof );
/* Insert a fake EOI marker */
Src-> buffer [0] = (joctet) 0xff;
Src-> buffer [1] = (joctet) jpeg_eoi;
Nbytes = 2;
}
Memcpy (SRC-> buffer, Src-> indata + Src-> ninoffset, nbytes );
Src-> ninoffset + = nbytes;
Src-> pub. next_input_byte = Src-> buffer;
Src-> pub. bytes_in_buffer = nbytes;
Src-> start_of_file = false;
Return true;
}
So far, we have completed all the changes in the source code of the libjpeg library, and the rest is to write a test program to test it.
3. Write test code
For detailed call steps of the libjpeg library, refer to my article "using jpeglib to compress images in JPG format". The above details the use of the libjpeg Library
The steps for image compression and decompression are as follows:
No matter whether it is compressed or decompressed, there is only one function that is different from the original libjpeg Library call, that is, calling the functions pai_stdio_dest and pai_stdio_src,
When calling the original libjpeg library, you need to provide the opened JPG file handles for these two functions. For the new libjpeg library, you do not need to open the JPG file. When compressing,
We need to provide a sufficient memory area for the libjpeg library. during decompression, we only need to provide the memory area for storing JPEG images to the libjpeg library. The following details
Call methods for the transformed functions pai_stdio_dest and pai_stdio_src.
1. pai_stdio_dest
Function prototype: void pai_stdio_dest (j_compress_ptr Cinfo, char * outdata, int * psize );
Here, outdata points to the memory zone we provide to the libjpeg library for storing compressed image data. This memory should be applied before we call this function. As you can see,
In the libjpeg library, we do not check for out-of-bounds access to the memory zone and must be large enough. Otherwise, the risk of out-of-bounds access to the memory may occur. After the entire image is compressed, psize
Returns the size of jpg image data. The test code is as follows:
Char outdata [1000000]; // used for caching. Set this parameter to 1000 kb. You can dynamically apply for this parameter in actual use.
Int nsize; // used to store the size of the compressed image data
..........
Pai_stdio_dest (& JCs, outdata, & nsize );
..........
2. pai_stdio_src
Function prototype: void pai_stdio_src (j_decompress_ptr Cinfo, char * indata, int nsize );
Here, indata points to the JPG data to be decompressed. The data can be directly read from the JPG file or compressed directly in the memory through the libjpeg library.
The generated data. nsize is of course the size of the JPG data. The test code is as follows:
..............
Char indata [1000000]; // used to store the image data before decompression. The data is directly read from the JPG file.
File * f = fopen (strsourcefilename, "rb ");
If (F = NULL)
{
Printf ("Open File error! /N ");
Return;
}
Int nsize = fread (outdata, f); // read jpg image data. nsize indicates the size of the actually read image data.
Fclose (f );
// The following code is used to decompress the image.
Performance_stdio_src (& Cinfo, outdata, nsize );
.............