1
/***************************************************************************
3
Regression test report generator
5
****************************************************************************
10
Redistribution and use in source and binary forms, with or without
11
modification, are permitted provided that the following conditions are
14
* Redistributions of source code must retain the above copyright
15
notice, this list of conditions and the following disclaimer.
16
* Redistributions in binary form must reproduce the above copyright
17
notice, this list of conditions and the following disclaimer in
18
the documentation and/or other materials provided with the
20
* Neither the name 'MAME' nor the names of its contributors may be
21
used to endorse or promote products derived from this software
22
without specific prior written permission.
24
THIS SOFTWARE IS PROVIDED BY AARON GILES ''AS IS'' AND ANY EXPRESS OR
25
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
26
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
27
DISCLAIMED. IN NO EVENT SHALL AARON GILES BE LIABLE FOR ANY DIRECT,
28
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
29
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
30
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
32
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
33
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34
POSSIBILITY OF SUCH DAMAGE.
36
****************************************************************************/
46
/***************************************************************************
48
***************************************************************************/
50
#define MAX_COMPARES 16
51
#define BITMAP_SPACE 4
55
STATUS_NOT_PRESENT = 0,
57
STATUS_SUCCESS_DIFFERENT,
61
STATUS_FAILED_VALIDITY,
73
BUCKET_CONSISTENT_ERROR,
75
BUCKET_GOOD_BUT_CHANGED,
76
BUCKET_GOOD_BUT_CHANGED_SCREENSHOTS,
82
/***************************************************************************
84
***************************************************************************/
86
typedef struct _summary_file summary_file;
92
UINT8 status[MAX_COMPARES];
93
UINT8 matchbitmap[MAX_COMPARES];
94
char * text[MAX_COMPARES];
95
UINT32 textsize[MAX_COMPARES];
96
UINT32 textalloc[MAX_COMPARES];
100
typedef struct _summary_list summary_list;
104
summary_file * files;
111
/***************************************************************************
113
***************************************************************************/
115
static summary_file *filehash[128][128];
116
static summary_list lists[MAX_COMPARES];
117
static int list_count;
119
static const char *const bucket_name[] =
122
"Games That Have Improved",
123
"Games That Have Regressed",
124
"Games With Changed Screenshots",
125
"Games With Multiple Errors",
126
"Games With Consistent Errors",
127
"Games That Are Consistently Good",
128
"Games That Regressed But Improved",
129
"Games With Changed Screenshots",
132
static const int bucket_output_order[] =
137
BUCKET_GOOD_BUT_CHANGED_SCREENSHOTS,
138
BUCKET_GOOD_BUT_CHANGED,
140
BUCKET_CONSISTENT_ERROR
143
static const char *const status_text[] =
151
"Failed Validity Check",
152
"Other Unknown Error"
155
static const char *const status_color[] =
158
"background:#00A000",
159
"background:#E0E000",
160
"background:#8000C0",
161
"background:#C00000",
162
"background:#C00000",
163
"background:#C06000",
164
"background:#C00000",
165
"background:#C00000",
170
/***************************************************************************
172
***************************************************************************/
174
/* summary parsing */
175
static int read_summary_log(const char *filename, int index);
176
static summary_file *parse_driver_tag(char *linestart, int index);
177
static summary_file *get_file(const char *filename);
178
static int CLIB_DECL compare_file(const void *file0ptr, const void *file1ptr);
179
static summary_file *sort_file_list(void);
182
static core_file *create_file_and_output_header(const astring *filename, const astring *templatefile, const astring *title);
183
static void output_footer_and_close_file(core_file *file, const astring *templatefile, const astring *title);
185
/* report generators */
186
static void output_report(const astring *dirname, const astring *tempheader, const astring *tempfooter, summary_file *filelist);
187
static int compare_screenshots(summary_file *curfile);
188
static int generate_png_diff(const summary_file *curfile, const astring *destdir, const char *destname);
189
static void create_linked_file(const astring *dirname, const summary_file *curfile, const summary_file *prevfile, const summary_file *nextfile, const char *pngfile, const astring *tempheader, const astring *tempfooter);
190
static void append_driver_list_table(const char *header, const astring *dirname, core_file *indexfile, const summary_file *listhead, const astring *tempheader, const astring *tempfooter);
194
/***************************************************************************
196
***************************************************************************/
198
/*-------------------------------------------------
199
trim_string - trim leading/trailing spaces
201
-------------------------------------------------*/
203
INLINE char *trim_string(char *string)
207
/* trim leading spaces */
208
while (*string != 0 && isspace((UINT8)*string))
211
/* trim trailing spaces */
212
length = strlen(string);
213
while (length > 0 && isspace((UINT8)string[length - 1]))
214
string[--length] = 0;
220
/*-------------------------------------------------
221
get_unique_index - get the unique bitmap
222
index for a given entry
223
-------------------------------------------------*/
225
INLINE int get_unique_index(const summary_file *curfile, int index)
227
int listnum, curindex = 0;
229
/* if we're invalid, just return that */
230
if (curfile->matchbitmap[index] == 0xff)
233
/* count unique elements up to us */
234
for (listnum = 0; listnum < curfile->matchbitmap[index]; listnum++)
235
if (curfile->matchbitmap[listnum] == listnum)
242
/***************************************************************************
244
***************************************************************************/
246
/*-------------------------------------------------
247
main - main entry point
248
-------------------------------------------------*/
250
int main(int argc, char *argv[])
252
astring *dirname = NULL, *tempfilename = NULL, *tempheader = NULL, *tempfooter = NULL;
258
/* first argument is the directory */
261
fprintf(stderr, "Usage:\nregrep <template> <outputdir> <summary1> [<summary2> [<summary3> ...]]\n");
264
tempfilename = astring_dupc(argv[1]);
265
dirname = astring_dupc(argv[2]);
266
list_count = argc - 3;
268
/* read the template file into an astring */
269
if (core_fload(astring_c(tempfilename), &buffer, &bufsize) == FILERR_NONE)
271
tempheader = astring_dupch((const char *)buffer, bufsize);
275
/* verify the template */
276
if (tempheader == NULL)
278
fprintf(stderr, "Unable to read template file\n");
281
result = astring_findc(tempheader, 0, "<!--CONTENT-->");
284
fprintf(stderr, "Template is missing a <!--CONTENT--> marker\n");
287
tempfooter = astring_substr(astring_dup(tempheader), result + 14, -1);
288
tempheader = astring_substr(tempheader, 0, result);
290
/* loop over arguments and read the files */
291
for (listnum = 0; listnum < list_count; listnum++)
293
result = read_summary_log(argv[listnum + 3], listnum);
298
/* output the summary */
299
output_report(dirname, tempheader, tempfooter, sort_file_list());
301
astring_free(dirname);
302
astring_free(tempfilename);
303
astring_free(tempheader);
304
astring_free(tempfooter);
310
/***************************************************************************
312
***************************************************************************/
314
/*-------------------------------------------------
315
get_file - lookup a driver name in the hash
316
table and return a pointer to it; if none
317
found, allocate a new entry
318
-------------------------------------------------*/
320
static summary_file *get_file(const char *filename)
324
/* use the first two characters as a lookup */
325
for (file = filehash[filename[0] & 0x7f][filename[1] & 0x7f]; file != NULL; file = file->next)
326
if (strcmp(filename, file->name) == 0)
329
/* didn't find one -- allocate */
330
file = (summary_file *)malloc(sizeof(*file));
333
memset(file, 0, sizeof(*file));
335
/* set the name so we find it in the future */
336
strcpy(file->name, filename);
338
/* add to the head of the list */
339
file->next = filehash[filename[0] & 0x7f][filename[1] & 0x7f];
340
filehash[filename[0] & 0x7f][filename[1] & 0x7f] = file;
345
/*-------------------------------------------------
346
read_summary_log - read a summary.log file
347
and build entries for its data
348
-------------------------------------------------*/
350
static int read_summary_log(const char *filename, int index)
352
summary_file *curfile = NULL;
353
char linebuffer[1024];
358
/* open the logfile */
359
file = fopen(filename, "r");
362
fprintf(stderr, "Error: file '%s' not found\n", filename);
367
while (fgets(linebuffer, sizeof(linebuffer), file) != NULL)
369
/* trim the leading/trailing spaces */
370
linestart = trim_string(linebuffer);
372
/* is this one of our specials? */
373
if (strncmp(linestart, "@@@@@", 5) == 0)
375
/* advance past the signature */
378
/* look for the driver= tag */
379
if (strncmp(linestart, "driver=", 7) == 0)
381
curfile = parse_driver_tag(linestart + 7, index);
387
/* look for the source= tag */
388
else if (strncmp(linestart, "source=", 7) == 0)
390
/* error if no driver yet */
393
fprintf(stderr, "Unexpected @@@@@source= tag\n");
397
/* copy the string */
398
strcpy(curfile->source, trim_string(linestart + 7));
401
/* look for the dir= tag */
402
else if (strncmp(linestart, "dir=", 4) == 0)
404
char *dirname = trim_string(linestart + 4);
406
/* allocate a copy of the string */
407
lists[index].dir = (char *)malloc(strlen(dirname) + 1);
408
if (lists[index].dir == NULL)
410
strcpy(lists[index].dir, dirname);
411
fprintf(stderr, "Directory %s\n", lists[index].dir);
415
/* if not, consider other options */
416
else if (curfile != NULL)
421
/* look for the pngcrc= tag */
422
if (strncmp(linestart, "pngcrc: ", 7) == 0)
426
/* otherwise, accumulate the text */
429
/* find the end of the line and normalize it with a CR */
430
for (curptr = linestart; *curptr != 0 && *curptr != '\n' && *curptr != '\r'; curptr++)
431
if (!isspace((UINT8)*curptr))
436
/* ignore blank lines */
440
/* see if we have enough room */
441
if (curfile->textsize[index] + (curptr - linestart) + 1 >= curfile->textalloc[index])
443
curfile->textalloc[index] = curfile->textsize[index] + (curptr - linestart) + 256;
444
curfile->text[index] = (char *)realloc(curfile->text[index], curfile->textalloc[index]);
445
if (curfile->text[index] == NULL)
447
fprintf(stderr, "Unable to allocate memory for text\n");
452
/* append our text */
453
strcpy(curfile->text[index] + curfile->textsize[index], linestart);
454
curfile->textsize[index] += curptr - linestart;
458
/* look for the M.A.M.E. header */
459
else if (strncmp(linestart, "M.A.M.E. v", 10) == 0)
461
char *start = linestart + 10;
465
for (end = start; !isspace((UINT8)*end); end++) ;
467
strcpy(lists[index].version, start);
468
fprintf(stderr, "Parsing results from version %s\n", lists[index].version);
473
fprintf(stderr, "Parsed %d drivers\n", drivers);
482
/*-------------------------------------------------
483
parse_driver_tag - parse the status info
485
-------------------------------------------------*/
487
static summary_file *parse_driver_tag(char *linestart, int index)
489
summary_file *curfile;
492
/* find the colon separating name from status */
493
colon = strchr(linestart, ':');
496
fprintf(stderr, "Unexpected text after @@@@@driver=\n");
500
/* NULL terminate at the colon and look up the file */
502
curfile = get_file(trim_string(linestart));
505
fprintf(stderr, "Unable to allocate memory for driver\n");
509
/* clear out any old status for this file */
510
curfile->status[index] = STATUS_NOT_PRESENT;
511
if (curfile->text[index] != NULL)
512
free(curfile->text[index]);
513
curfile->text[index] = NULL;
514
curfile->textsize[index] = 0;
515
curfile->textalloc[index] = 0;
517
/* strip leading/trailing spaces from the status */
518
colon = trim_string(colon + 1);
520
/* convert status into statistics */
521
if (strcmp(colon, "Success") == 0)
522
curfile->status[index] = STATUS_SUCCESS;
523
else if (strcmp(colon, "Missing files") == 0)
524
curfile->status[index] = STATUS_MISSING_FILES;
525
else if (strcmp(colon, "Exception") == 0)
526
curfile->status[index] = STATUS_EXCEPTION;
527
else if (strcmp(colon, "Fatal error") == 0)
528
curfile->status[index] = STATUS_FATAL_ERROR;
529
else if (strcmp(colon, "Failed validity check") == 0)
530
curfile->status[index] = STATUS_FAILED_VALIDITY;
532
curfile->status[index] = STATUS_OTHER;
538
/*-------------------------------------------------
539
compare_file - compare two files, sorting
540
first by source filename, then by driver name
541
-------------------------------------------------*/
543
static int CLIB_DECL compare_file(const void *file0ptr, const void *file1ptr)
545
summary_file *file0 = *(summary_file **)file0ptr;
546
summary_file *file1 = *(summary_file **)file1ptr;
547
int result = strcmp(file0->source, file1->source);
549
result = strcmp(file0->name, file1->name);
554
/*-------------------------------------------------
555
sort_file_list - convert the hashed lists
556
into a single, sorted list
557
-------------------------------------------------*/
559
static summary_file *sort_file_list(void)
561
summary_file *listhead, **tailptr, *curfile, **filearray;
562
int numfiles, filenum;
565
/* count the total number of files */
567
for (c0 = 0; c0 < 128; c0++)
568
for (c1 = 0; c1 < 128; c1++)
569
for (curfile = filehash[c0][c1]; curfile != NULL; curfile = curfile->next)
572
/* allocate an array of files */
573
filearray = (summary_file **)malloc(numfiles * sizeof(*filearray));
574
if (filearray == NULL)
576
fprintf(stderr, "Out of memory!\n");
580
/* populate the array */
582
for (c0 = 0; c0 < 128; c0++)
583
for (c1 = 0; c1 < 128; c1++)
584
for (curfile = filehash[c0][c1]; curfile != NULL; curfile = curfile->next)
585
filearray[numfiles++] = curfile;
588
qsort(filearray, numfiles, sizeof(filearray[0]), compare_file);
590
/* now regenerate a single list */
593
for (filenum = 0; filenum < numfiles; filenum++)
595
*tailptr = filearray[filenum];
596
tailptr = &(*tailptr)->next;
606
/***************************************************************************
608
***************************************************************************/
610
/*-------------------------------------------------
611
create_file_and_output_header - create a new
612
HTML file with a standard header
613
-------------------------------------------------*/
615
static core_file *create_file_and_output_header(const astring *filename, const astring *templatefile, const astring *title)
620
/* create the indexfile */
621
if (core_fopen(astring_c(filename), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS | OPEN_FLAG_NO_BOM, &file) != FILERR_NONE)
625
modified = astring_dup(templatefile);
626
astring_replacec(modified, 0, "<!--TITLE-->", astring_c(title));
627
core_fwrite(file, astring_c(modified), astring_len(modified));
629
/* return the file */
630
astring_free(modified);
635
/*-------------------------------------------------
636
output_footer_and_close_file - write a
637
standard footer to an HTML file and close it
638
-------------------------------------------------*/
640
static void output_footer_and_close_file(core_file *file, const astring *templatefile, const astring *title)
644
modified = astring_dup(templatefile);
645
astring_replacec(modified, 0, "<!--TITLE-->", astring_c(title));
646
core_fwrite(file, astring_c(modified), astring_len(modified));
647
astring_free(modified);
653
/***************************************************************************
655
***************************************************************************/
657
/*-------------------------------------------------
658
output_report - generate the summary
660
-------------------------------------------------*/
662
static void output_report(const astring *dirname, const astring *tempheader, const astring *tempfooter, summary_file *filelist)
664
summary_file *buckethead[BUCKET_COUNT], **buckettailptr[BUCKET_COUNT];
665
summary_file *curfile;
666
astring *title = astring_dupc("MAME Regressions");
667
astring *tempname = astring_alloc();
668
int listnum, bucknum;
669
core_file *indexfile;
670
int count = 0, total;
672
/* initialize the lists */
673
for (bucknum = 0; bucknum < BUCKET_COUNT; bucknum++)
675
buckethead[bucknum] = NULL;
676
buckettailptr[bucknum] = &buckethead[bucknum];
679
/* compute the total number of files */
681
for (curfile = filelist; curfile != NULL; curfile = curfile->next)
684
/* first bucketize the games */
685
for (curfile = filelist; curfile != NULL; curfile = curfile->next)
687
int statcount[STATUS_COUNT] = { 0 };
688
int bucket = BUCKET_UNKNOWN;
689
int unique_codes = 0;
693
if (++count % 100 == 0)
694
fprintf(stderr, "Processing file %d/%d\n", count, total);
696
/* find the first valid entry */
697
for (first_valid = 0; curfile->status[first_valid] == STATUS_NOT_PRESENT; first_valid++) ;
699
/* do we need to output anything? */
700
for (listnum = first_valid; listnum < list_count; listnum++)
701
if (statcount[curfile->status[listnum]]++ == 0)
704
/* were we consistent? */
705
if (unique_codes == 1)
707
/* were we consistently ok? */
708
if (curfile->status[first_valid] == STATUS_SUCCESS)
709
bucket = compare_screenshots(curfile);
711
/* must have been consistently erroring */
713
bucket = BUCKET_CONSISTENT_ERROR;
716
/* ok, we're not consistent; could be a number of things */
719
/* were we ok at the start and end but not in the middle? */
720
if (curfile->status[first_valid] == STATUS_SUCCESS && curfile->status[list_count - 1] == STATUS_SUCCESS)
721
bucket = BUCKET_GOOD_BUT_CHANGED;
723
/* did we go from good to bad? */
724
else if (curfile->status[first_valid] == STATUS_SUCCESS)
725
bucket = BUCKET_REGRESSED;
727
/* did we go from bad to good? */
728
else if (curfile->status[list_count - 1] == STATUS_SUCCESS)
729
bucket = BUCKET_IMPROVED;
731
/* must have had multiple errors */
733
bucket = BUCKET_MULTI_ERROR;
736
/* add us to the appropriate list */
737
*buckettailptr[bucket] = curfile;
738
buckettailptr[bucket] = &curfile->next;
741
/* terminate all the lists */
742
for (bucknum = 0; bucknum < BUCKET_COUNT; bucknum++)
743
*buckettailptr[bucknum] = NULL;
746
astring_printf(tempname, "%s" PATH_SEPARATOR "%s", astring_c(dirname), "index.html");
747
indexfile = create_file_and_output_header(tempname, tempheader, title);
748
if (indexfile == NULL)
750
fprintf(stderr, "Error creating file '%s'\n", astring_c(tempname));
751
astring_free(tempname);
756
/* iterate over buckets and output them */
757
for (bucknum = 0; bucknum < ARRAY_LENGTH(bucket_output_order); bucknum++)
759
int curbucket = bucket_output_order[bucknum];
761
if (buckethead[curbucket] != NULL)
763
fprintf(stderr, "Outputting bucket: %s\n", bucket_name[curbucket]);
764
append_driver_list_table(bucket_name[curbucket], dirname, indexfile, buckethead[curbucket], tempheader, tempfooter);
769
output_footer_and_close_file(indexfile, tempfooter, title);
770
astring_free(tempname);
775
/*-------------------------------------------------
776
compare_screenshots - compare the screenshots
777
for all the games in a file
778
-------------------------------------------------*/
780
static int compare_screenshots(summary_file *curfile)
782
bitmap_t *bitmaps[MAX_COMPARES];
783
int unique[MAX_COMPARES];
787
/* iterate over all files and load their bitmaps */
788
for (listnum = 0; listnum < list_count; listnum++)
790
bitmaps[listnum] = NULL;
791
if (curfile->status[listnum] == STATUS_SUCCESS)
793
astring *fullname = astring_alloc();
797
/* get the filename for the image */
798
astring_printf(fullname, "%s" PATH_SEPARATOR "snap" PATH_SEPARATOR "%s" PATH_SEPARATOR "final.png", lists[listnum].dir, curfile->name);
801
filerr = core_fopen(astring_c(fullname), OPEN_FLAG_READ, &file);
803
/* if that failed, look in the old location */
804
if (filerr != FILERR_NONE)
806
/* get the filename for the image */
807
astring_printf(fullname, "%s" PATH_SEPARATOR "snap" PATH_SEPARATOR "_%s.png", lists[listnum].dir, curfile->name);
810
filerr = core_fopen(astring_c(fullname), OPEN_FLAG_READ, &file);
813
/* if that worked, load the file */
814
if (filerr == FILERR_NONE)
816
png_read_bitmap(file, &bitmaps[listnum]);
819
astring_free(fullname);
823
/* now find all the different bitmap types */
824
for (listnum = 0; listnum < list_count; listnum++)
826
curfile->matchbitmap[listnum] = 0xff;
827
if (bitmaps[listnum] != NULL)
829
bitmap_t *this_bitmap = bitmaps[listnum];
832
/* compare against all unique bitmaps */
833
for (compnum = 0; compnum < numunique; compnum++)
835
bitmap_t *base_bitmap = bitmaps[unique[compnum]];
839
/* if the sizes are different, we differ; otherwise start off assuming we are the same */
840
bitmaps_differ = (this_bitmap->width != base_bitmap->width || this_bitmap->height != base_bitmap->height);
842
/* compare scanline by scanline */
843
for (y = 0; y < this_bitmap->height && !bitmaps_differ; y++)
845
UINT32 *base = BITMAP_ADDR32(base_bitmap, y, 0);
846
UINT32 *curr = BITMAP_ADDR32(this_bitmap, y, 0);
848
/* scan the scanline */
849
for (x = 0; x < this_bitmap->width; x++)
850
if (*base++ != *curr++)
852
bitmaps_differ = (x != this_bitmap->width);
855
/* if we matched, remember which listnum index we matched, and stop */
858
curfile->matchbitmap[listnum] = unique[compnum];
862
/* if different from the first unique entry, adjust the status */
863
if (bitmaps_differ && compnum == 0)
864
curfile->status[listnum] = STATUS_SUCCESS_DIFFERENT;
867
/* if we're unique, add ourselves to the list */
868
if (compnum >= numunique)
870
unique[numunique++] = listnum;
871
curfile->matchbitmap[listnum] = listnum;
877
/* free the bitmaps */
878
for (listnum = 0; listnum < list_count; listnum++)
879
if (bitmaps[listnum] != NULL)
880
bitmap_free(bitmaps[listnum]);
882
/* if all screenshots matched, we're good */
886
/* if the last screenshot matched the first unique one, we're good but changed */
887
if (curfile->matchbitmap[listnum - 1] == unique[0])
888
return BUCKET_GOOD_BUT_CHANGED_SCREENSHOTS;
890
/* otherwise we're just changed */
891
return BUCKET_CHANGED;
895
/*-------------------------------------------------
896
generate_png_diff - create a new PNG file
897
that shows multiple differing PNGs side by
898
side with a third set of differences
899
-------------------------------------------------*/
901
static int generate_png_diff(const summary_file *curfile, const astring *destdir, const char *destname)
903
bitmap_t *bitmaps[MAX_COMPARES] = { NULL };
904
astring *srcimgname = astring_alloc();
905
astring *dstfilename = astring_alloc();
906
astring *tempname = astring_alloc();
907
bitmap_t *finalbitmap = NULL;
908
int width, height, maxwidth;
911
core_file *file = NULL;
917
/* generate the common source filename */
918
astring_printf(dstfilename, "%s" PATH_SEPARATOR "%s", astring_c(destdir), destname);
919
astring_printf(srcimgname, "snap" PATH_SEPARATOR "%s" PATH_SEPARATOR "final.png", curfile->name);
921
/* open and load all unique bitmaps */
922
for (listnum = 0; listnum < list_count; listnum++)
923
if (curfile->matchbitmap[listnum] == listnum)
925
astring_printf(tempname, "%s" PATH_SEPARATOR "%s", lists[listnum].dir, astring_c(srcimgname));
927
/* open the source image */
928
filerr = core_fopen(astring_c(tempname), OPEN_FLAG_READ, &file);
929
if (filerr != FILERR_NONE)
932
/* load the source image */
933
pngerr = png_read_bitmap(file, &bitmaps[bitmapcount++]);
935
if (pngerr != PNGERR_NONE)
939
/* if there's only one unique bitmap, skip it */
940
if (bitmapcount <= 1)
943
/* determine the size of the final bitmap */
945
maxwidth = bitmaps[0]->width;
946
for (bmnum = 1; bmnum < bitmapcount; bmnum++)
950
/* determine the maximal width */
951
maxwidth = MAX(maxwidth, bitmaps[bmnum]->width);
952
curwidth = bitmaps[0]->width + BITMAP_SPACE + maxwidth + BITMAP_SPACE + maxwidth;
953
width = MAX(width, curwidth);
955
/* add to the height */
956
height += MAX(bitmaps[0]->height, bitmaps[bmnum]->height);
958
height += BITMAP_SPACE;
961
/* allocate the final bitmap */
962
finalbitmap = bitmap_alloc(width, height, BITMAP_FORMAT_ARGB32);
963
if (finalbitmap == NULL)
966
/* now copy and compare each set of bitmaps */
968
for (bmnum = 1; bmnum < bitmapcount; bmnum++)
970
bitmap_t *bitmap1 = bitmaps[0];
971
bitmap_t *bitmap2 = bitmaps[bmnum];
972
int curheight = MAX(bitmap1->height, bitmap2->height);
975
/* iterate over rows in these bitmaps */
976
for (y = 0; y < curheight; y++)
978
UINT32 *src1 = (y < bitmap1->height) ? BITMAP_ADDR32(bitmap1, y, 0) : NULL;
979
UINT32 *src2 = (y < bitmap2->height) ? BITMAP_ADDR32(bitmap2, y, 0) : NULL;
980
UINT32 *dst1 = BITMAP_ADDR32(finalbitmap, starty + y, 0);
981
UINT32 *dst2 = BITMAP_ADDR32(finalbitmap, starty + y, bitmap1->width + BITMAP_SPACE);
982
UINT32 *dstdiff = BITMAP_ADDR32(finalbitmap, starty + y, bitmap1->width + BITMAP_SPACE + maxwidth + BITMAP_SPACE);
984
/* now iterate over columns */
985
for (x = 0; x < maxwidth; x++)
987
int pix1 = -1, pix2 = -2;
989
if (src1 != NULL && x < bitmap1->width)
990
pix1 = dst1[x] = src1[x];
991
if (src2 != NULL && x < bitmap2->width)
992
pix2 = dst2[x] = src2[x];
993
dstdiff[x] = (pix1 != pix2) ? 0xffffffff : 0xff000000;
997
/* update the starting Y position */
998
starty += BITMAP_SPACE + MAX(bitmap1->height, bitmap2->height);
1001
/* write the final PNG */
1002
filerr = core_fopen(astring_c(dstfilename), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE, &file);
1003
if (filerr != FILERR_NONE)
1005
pngerr = png_write_bitmap(file, NULL, finalbitmap, 0, NULL);
1007
if (pngerr != PNGERR_NONE)
1010
/* if we get here, we are error free */
1014
if (finalbitmap != NULL)
1015
bitmap_free(finalbitmap);
1016
for (bmnum = 0; bmnum < bitmapcount; bmnum++)
1017
if (bitmaps[bmnum] != NULL)
1018
bitmap_free(bitmaps[bmnum]);
1020
osd_rmfile(astring_c(dstfilename));
1021
astring_free(dstfilename);
1022
astring_free(srcimgname);
1023
astring_free(tempname);
1028
/*-------------------------------------------------
1029
create_linked_file - create a comparison
1030
file between differing versions
1031
-------------------------------------------------*/
1033
static void create_linked_file(const astring *dirname, const summary_file *curfile, const summary_file *prevfile, const summary_file *nextfile, const char *pngfile, const astring *tempheader, const astring *tempfooter)
1035
astring *linkname = astring_alloc();
1036
astring *filename = astring_alloc();
1037
astring *title = astring_alloc();
1038
core_file *linkfile;
1041
/* create the filename */
1042
astring_printf(filename, "%s.html", curfile->name);
1045
astring_printf(title, "%s Regressions (%s)", curfile->name, curfile->source);
1046
astring_printf(linkname, "%s" PATH_SEPARATOR "%s", astring_c(dirname), astring_c(filename));
1047
linkfile = create_file_and_output_header(linkname, tempheader, title);
1048
if (linkfile == NULL)
1050
fprintf(stderr, "Error creating file '%s'\n", astring_c(filename));
1051
astring_free(title);
1052
astring_free(filename);
1053
astring_free(linkname);
1057
/* link to the previous/next entries */
1058
core_fprintf(linkfile, "\t<p>\n");
1059
core_fprintf(linkfile, "\t<table width=\"100%%\">\n");
1060
core_fprintf(linkfile, "\t\t<td align=\"left\" width=\"40%%\" style=\"border:none\">");
1061
if (prevfile != NULL)
1062
core_fprintf(linkfile, "<a href=\"%s.html\"><< %s (%s)</a>", prevfile->name, prevfile->name, prevfile->source);
1063
core_fprintf(linkfile, "</td>\n");
1064
core_fprintf(linkfile, "\t\t<td align=\"center\" width=\"20%%\" style=\"border:none\"><a href=\"index.html\">Home</a></td>\n");
1065
core_fprintf(linkfile, "\t\t<td align=\"right\" width=\"40%%\" style=\"border:none\">");
1066
if (nextfile != NULL)
1067
core_fprintf(linkfile, "<a href=\"%s.html\">%s (%s) >></a>", nextfile->name, nextfile->name, nextfile->source);
1068
core_fprintf(linkfile, "</td>\n");
1069
core_fprintf(linkfile, "\t</table>\n");
1070
core_fprintf(linkfile, "\t</p>\n");
1072
/* output data for each one */
1073
for (listnum = 0; listnum < list_count; listnum++)
1075
int imageindex = -1;
1077
/* generate the HTML */
1078
core_fprintf(linkfile, "\n\t<h2>%s</h2>\n", lists[listnum].version);
1079
core_fprintf(linkfile, "\t<p>\n");
1080
core_fprintf(linkfile, "\t<b>Status:</b> %s\n", status_text[curfile->status[listnum]]);
1081
if (pngfile != NULL)
1082
imageindex = get_unique_index(curfile, listnum);
1083
if (imageindex != -1)
1084
core_fprintf(linkfile, " [%d]", imageindex);
1085
core_fprintf(linkfile, "\t</p>\n");
1086
if (curfile->text[listnum] != NULL)
1088
core_fprintf(linkfile, "\t<p>\n");
1089
core_fprintf(linkfile, "\t<b>Errors:</b>\n");
1090
core_fprintf(linkfile, "\t<pre>%s</pre>\n", curfile->text[listnum]);
1091
core_fprintf(linkfile, "\t</p>\n");
1095
/* output link to the image */
1096
if (pngfile != NULL)
1098
core_fprintf(linkfile, "\n\t<h2>Screenshot Comparisons</h2>\n");
1099
core_fprintf(linkfile, "\t<p>\n");
1100
core_fprintf(linkfile, "\t<img src=\"%s\" />\n", pngfile);
1101
core_fprintf(linkfile, "\t</p>\n");
1105
output_footer_and_close_file(linkfile, tempfooter, title);
1106
astring_free(title);
1107
astring_free(filename);
1108
astring_free(linkname);
1112
/*-------------------------------------------------
1113
append_driver_list_table - append a table
1114
of drivers from a list to an HTML file
1115
-------------------------------------------------*/
1117
static void append_driver_list_table(const char *header, const astring *dirname, core_file *indexfile, const summary_file *listhead, const astring *tempheader, const astring *tempfooter)
1119
const summary_file *curfile, *prevfile;
1120
int width = 100 / (2 + list_count);
1123
/* output a header */
1124
core_fprintf(indexfile, "\t<h2>%s</h2>\n", header);
1126
/* start the table */
1127
core_fprintf(indexfile, "\t<p><table width=\"90%%\">\n");
1128
core_fprintf(indexfile, "\t\t<tr>\n\t\t\t<th width=\"%d%%\">Source</th><th width=\"%d%%\">Driver</th>", width, width);
1129
for (listnum = 0; listnum < list_count; listnum++)
1130
core_fprintf(indexfile, "<th width=\"%d%%\">%s</th>", width, lists[listnum].version);
1131
core_fprintf(indexfile, "\n\t\t</tr>\n");
1133
/* if nothing, print a default message */
1134
if (listhead == NULL)
1136
core_fprintf(indexfile, "\t\t<tr>\n\t\t\t");
1137
core_fprintf(indexfile, "<td colspan=\"%d\" align=\"center\">(No regressions detected)</td>", list_count + 2);
1138
core_fprintf(indexfile, "\n\t\t</tr>\n");
1141
/* iterate over files */
1142
for (prevfile = NULL, curfile = listhead; curfile != NULL; prevfile = curfile, curfile = curfile->next)
1144
int rowspan = 0, uniqueshots = 0;
1145
char pngdiffname[40];
1147
/* if this is the first entry in this source file, count how many rows we need to span */
1148
if (prevfile == NULL || strcmp(prevfile->source, curfile->source) != 0)
1150
const summary_file *cur;
1151
for (cur = curfile; cur != NULL; cur = cur->next)
1152
if (strcmp(cur->source, curfile->source) == 0)
1158
/* create screenshots if necessary */
1160
for (listnum = 0; listnum < list_count; listnum++)
1161
if (curfile->matchbitmap[listnum] == listnum)
1163
if (uniqueshots > 1)
1165
sprintf(pngdiffname, "compare_%s.png", curfile->name);
1166
if (generate_png_diff(curfile, dirname, pngdiffname) != 0)
1170
/* create a linked file */
1171
create_linked_file(dirname, curfile, prevfile, curfile->next, (pngdiffname[0] == 0) ? NULL : pngdiffname, tempheader, tempfooter);
1174
core_fprintf(indexfile, "\t\t<tr>\n\t\t\t");
1176
core_fprintf(indexfile, "<td rowspan=\"%d\">%s</td>", rowspan, curfile->source);
1177
core_fprintf(indexfile, "<td><a href=\"%s.html\">%s</a></td>", curfile->name, curfile->name);
1178
for (listnum = 0; listnum < list_count; listnum++)
1180
int unique_index = -1;
1182
if (pngdiffname[0] != 0)
1183
unique_index = get_unique_index(curfile, listnum);
1184
if (unique_index != -1)
1185
core_fprintf(indexfile, "<td><span style=\"%s\"> </span> %s [<a href=\"%s\" target=\"blank\">%d</a>]</td>", status_color[curfile->status[listnum]], status_text[curfile->status[listnum]], pngdiffname, unique_index);
1187
core_fprintf(indexfile, "<td><span style=\"%s\"> </span> %s</td>", status_color[curfile->status[listnum]], status_text[curfile->status[listnum]]);
1189
core_fprintf(indexfile, "\n\t\t</tr>\n");
1191
/* also print the name and source file */
1192
printf("%s %s\n", curfile->name, curfile->source);
1196
core_fprintf(indexfile, "</table></p>\n");