-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathjpeg_blockdiff.c
More file actions
248 lines (213 loc) · 8.09 KB
/
jpeg_blockdiff.c
File metadata and controls
248 lines (213 loc) · 8.09 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
/* jpeg_blockdiff.c : compare two JPEG buffers and output difference blocks */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <jpeglib.h>
#define THRESH 25 /* threshold for difference detection */
/* ==== JPEG context structure ==== */
typedef struct {
struct jpeg_decompress_struct cinfo;
struct jpeg_error_mgr jerr;
jvirt_barray_ptr *coef;
jpeg_component_info *y;
unsigned char *buffer; /* memory buffer with JPEG data */
unsigned long buffer_size;
} JCtx;
/* JPEG memory source functions */
static void init_source(j_decompress_ptr cinfo) {}
static boolean fill_input_buffer(j_decompress_ptr cinfo) {
return TRUE;
}
static void skip_input_data(j_decompress_ptr cinfo, long num_bytes) {
struct jpeg_source_mgr* src = (struct jpeg_source_mgr*) cinfo->src;
if (num_bytes > 0) {
src->next_input_byte += (size_t) num_bytes;
src->bytes_in_buffer -= (size_t) num_bytes;
}
}
static void term_source(j_decompress_ptr cinfo) {}
/* Initialize memory source */
void jpeg_memory_src(j_decompress_ptr cinfo, unsigned char * buffer, unsigned long buffer_size) {
struct jpeg_source_mgr* src;
if (cinfo->src == NULL) {
cinfo->src = (struct jpeg_source_mgr *)
(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
sizeof(struct jpeg_source_mgr));
}
src = (struct jpeg_source_mgr*) cinfo->src;
src->init_source = init_source;
src->fill_input_buffer = fill_input_buffer;
src->skip_input_data = skip_input_data;
src->resync_to_restart = jpeg_resync_to_restart; /* use default method */
src->term_source = term_source;
src->bytes_in_buffer = buffer_size;
src->next_input_byte = buffer;
}
/* Open JPEG from memory buffer */
int open_jpeg_buffer(unsigned char *buffer, unsigned long buffer_size, JCtx *ctx, int want_coeffs)
{
ctx->buffer = buffer;
ctx->buffer_size = buffer_size;
ctx->cinfo.err = jpeg_std_error(&ctx->jerr);
jpeg_create_decompress(&ctx->cinfo);
jpeg_memory_src(&ctx->cinfo, buffer, buffer_size);
if (jpeg_read_header(&ctx->cinfo, TRUE) != JPEG_HEADER_OK) return 0;
if (want_coeffs) {
ctx->coef = jpeg_read_coefficients(&ctx->cinfo);
ctx->y = &ctx->cinfo.comp_info[0];
}
return 1;
}
void close_jpeg(JCtx *ctx) {
jpeg_destroy_decompress(&ctx->cinfo);
}
/* ========================================== */
/*
* Compare two JPEG images and write differences to output buffer in RGBA format
*
* buffer1: First JPEG buffer
* buffer1_size: Size of first buffer
* buffer2: Second JPEG buffer to compare against
* buffer2_size: Size of second buffer
* output_buffer: RGBA buffer where difference will be written (must be pre-allocated)
* output_width: Width of output buffer
* output_height: Height of output buffer
*
* Returns: 0 on success, 1 on failure
*/
int jpeg_compare_diff(
unsigned char *buffer1, unsigned long buffer1_size,
unsigned char *buffer2, unsigned long buffer2_size,
unsigned char *output_buffer, int output_width, int output_height)
{
/* 1) Open both JPEGs for coefficient access */
JCtx A={0}, B={0};
if (!open_jpeg_buffer(buffer1, buffer1_size, &A, 1) ||
!open_jpeg_buffer(buffer2, buffer2_size, &B, 1)) {
return 1;
}
/* Common block count */
int rows = A.y->height_in_blocks < B.y->height_in_blocks ?
A.y->height_in_blocks : B.y->height_in_blocks;
int cols = A.y->width_in_blocks < B.y->width_in_blocks ?
A.y->width_in_blocks : B.y->width_in_blocks;
/* 2) Create mask for changed blocks */
unsigned char *mask = calloc(rows*cols, 1); /* 0 = same, 1 = different */
if (!mask) { perror("mask"); return 1; }
for (int r=0; r<rows; ++r) {
JBLOCKARRAY ra = (A.cinfo.mem->access_virt_barray)
((j_common_ptr)&A.cinfo, A.coef[0], r, 1, FALSE);
JBLOCKARRAY rb = (B.cinfo.mem->access_virt_barray)
((j_common_ptr)&B.cinfo, B.coef[0], r, 1, FALSE);
for (int c=0; c<cols; ++c) {
JCOEF *ba = ra[0][c], *bb = rb[0][c];
int d0 = abs(ba[0]-bb[0]);
int d1 = abs(ba[1]-bb[1]);
int d2 = abs(ba[2]-bb[2]);
if (d0>THRESH || d1>THRESH || d2>THRESH)
mask[r*cols+c]=1;
}
}
/* Coefficient structures no longer needed */
close_jpeg(&A);
/* 3) Reopen second file and fully decompress (RGB) */
JCtx B2={0};
if (!open_jpeg_buffer(buffer2, buffer2_size, &B2, 0)) {
free(mask);
close_jpeg(&B);
return 1;
}
jpeg_start_decompress(&B2.cinfo);
int w = B2.cinfo.output_width,
h = B2.cinfo.output_height,
stride = w * 3;
/* Allocate source buffer for decompressed image */
unsigned char *src = malloc(h*stride);
if (!src) {
free(mask);
close_jpeg(&B);
close_jpeg(&B2);
return 1;
}
unsigned char *ptrs[1]; ptrs[0]=src;
while (B2.cinfo.output_scanline < h)
jpeg_read_scanlines(&B2.cinfo, ptrs + B2.cinfo.output_scanline, 1);
jpeg_finish_decompress(&B2.cinfo);
/* 4) Clear output buffer to transparent black (RGBA) */
memset(output_buffer, 0, output_width * output_height * 4);
/* Use the smaller dimensions between output buffer and source image */
int use_w = (w < output_width) ? w : output_width;
int use_h = (h < output_height) ? h : output_height;
/* 5) If mask is true, copy 8×8 block and set alpha to 255 */
for (int r=0; r<rows && r*8<use_h; ++r) {
for (int c=0; c<cols && c*8<use_w; ++c) {
if (mask[r*cols+c]) {
int py = r*8, px = c*8;
for (int y=0; y<8 && py+y<use_h; ++y) {
for (int x=0; x<8 && px+x<use_w; ++x) {
/* Source index (RGB) */
int src_idx = (py+y)*stride + (px+x)*3;
/* Destination index (RGBA) */
int dst_idx = (py+y)*output_width*4 + (px+x)*4;
/* Copy RGB and set alpha to 255 */
output_buffer[dst_idx] = src[src_idx]; /* R */
output_buffer[dst_idx+1] = src[src_idx+1]; /* G */
output_buffer[dst_idx+2] = src[src_idx+2]; /* B */
output_buffer[dst_idx+3] = 255; /* A - fully opaque */
}
}
}
}
}
/* Clean up */
free(mask);
free(src);
close_jpeg(&B);
close_jpeg(&B2);
return 0;
}
/* Legacy main function for testing, can be removed if not needed */
int main_jpeg_diff(int argc, char **argv)
{
if (argc != 3) {
fprintf(stderr,"Usage: %s a.jpg b.jpg\n", argv[0]);
return 1;
}
/* Read file 1 */
FILE *fp1 = fopen(argv[1], "rb");
if (!fp1) { perror(argv[1]); return 1; }
fseek(fp1, 0, SEEK_END);
long file1_size = ftell(fp1);
fseek(fp1, 0, SEEK_SET);
unsigned char *buffer1 = malloc(file1_size);
fread(buffer1, 1, file1_size, fp1);
fclose(fp1);
/* Read file 2 */
FILE *fp2 = fopen(argv[2], "rb");
if (!fp2) { perror(argv[2]); free(buffer1); return 1; }
fseek(fp2, 0, SEEK_END);
long file2_size = ftell(fp2);
fseek(fp2, 0, SEEK_SET);
unsigned char *buffer2 = malloc(file2_size);
fread(buffer2, 1, file2_size, fp2);
fclose(fp2);
/* Output buffer (assume 640x480 RGBA) */
int out_w = 640, out_h = 480;
unsigned char *output = malloc(out_w * out_h * 4);
if (jpeg_compare_diff(buffer1, file1_size, buffer2, file2_size, output, out_w, out_h) == 0) {
printf("Comparison completed successfully\n");
/* For testing: write raw RGBA to file */
FILE *fp_out = fopen("diff.rgba", "wb");
if (fp_out) {
fwrite(output, 1, out_w * out_h * 4, fp_out);
fclose(fp_out);
printf("Wrote diff.rgba\n");
}
} else {
printf("Comparison failed\n");
}
free(buffer1);
free(buffer2);
free(output);
return 0;
}