Skip to content

Commit 763c7c1

Browse files
kjkclaude
andcommitted
Save the raster inside an SVG wrapper (#6330)
A fixed-layout EPUB page is an <svg> around one JPEG. Save Image and copy got that svg re-rasterized at the layout size, so the file was a small PNG. fz_unwrap_display_list_image returns the one raster a display-list image draws, when it draws nothing else, with the matrix to the wrapper's unit square. The engine's image lookup uses it, so the original bytes and full resolution reach the save dialog and the clipboard. prompt: improve "Save image" for #6330 so it keeps the original image Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 parent 060cb95 commit 763c7c1

9 files changed

Lines changed: 293 additions & 7 deletions

File tree

‎ext/mupdf/include/mupdf/fitz/display-list.h‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,12 @@ fz_rect fz_bound_display_list(fz_context *ctx, fz_display_list *list);
130130
*/
131131
fz_image *fz_new_image_from_display_list(fz_context *ctx, float w, float h, fz_display_list *list);
132132

133+
/**
134+
SumatraPDF: the single raster a display-list image draws, or NULL.
135+
ctm maps the raster's unit square to the display-list image's.
136+
*/
137+
fz_image *fz_unwrap_display_list_image(fz_context *ctx, fz_image *image, fz_matrix *ctm);
138+
133139
/**
134140
Check for a display list being empty
135141

‎ext/mupdf/source/fitz/image.c‎

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1876,3 +1876,104 @@ fz_image *fz_new_image_from_display_list(fz_context *ctx, float w, float h, fz_d
18761876

18771877
return &image->super;
18781878
}
1879+
1880+
/* SumatraPDF: a display-list image (an SVG) that is one raster and nothing
1881+
* else. Save Image then keeps the raster's bytes and resolution (#6330). */
1882+
typedef struct
1883+
{
1884+
fz_device super;
1885+
fz_image *image;
1886+
fz_matrix ctm;
1887+
int images;
1888+
int other;
1889+
} unwrap_device;
1890+
1891+
static void
1892+
unwrap_fill_image(fz_context *ctx, fz_device *dev_, fz_image *img, fz_matrix ctm, float alpha, fz_color_params color_params)
1893+
{
1894+
unwrap_device *dev = (unwrap_device *)dev_;
1895+
if (dev->images++ > 0 || alpha != 1)
1896+
{
1897+
dev->other = 1;
1898+
return;
1899+
}
1900+
dev->image = fz_keep_image(ctx, img);
1901+
dev->ctm = ctm;
1902+
}
1903+
1904+
static void
1905+
unwrap_other(unwrap_device *dev)
1906+
{
1907+
dev->other = 1;
1908+
}
1909+
1910+
static void unwrap_fill_path(fz_context *ctx, fz_device *dev, const fz_path *path, int even_odd, fz_matrix ctm, fz_colorspace *cs, const float *color, float alpha, fz_color_params cp) { unwrap_other((unwrap_device *)dev); }
1911+
static void unwrap_stroke_path(fz_context *ctx, fz_device *dev, const fz_path *path, const fz_stroke_state *ss, fz_matrix ctm, fz_colorspace *cs, const float *color, float alpha, fz_color_params cp) { unwrap_other((unwrap_device *)dev); }
1912+
static void unwrap_clip_path(fz_context *ctx, fz_device *dev, const fz_path *path, int even_odd, fz_matrix ctm, fz_rect scissor) { unwrap_other((unwrap_device *)dev); }
1913+
static void unwrap_clip_stroke_path(fz_context *ctx, fz_device *dev, const fz_path *path, const fz_stroke_state *ss, fz_matrix ctm, fz_rect scissor) { unwrap_other((unwrap_device *)dev); }
1914+
static void unwrap_fill_text(fz_context *ctx, fz_device *dev, const fz_text *text, fz_matrix ctm, fz_colorspace *cs, const float *color, float alpha, fz_color_params cp) { unwrap_other((unwrap_device *)dev); }
1915+
static void unwrap_stroke_text(fz_context *ctx, fz_device *dev, const fz_text *text, const fz_stroke_state *ss, fz_matrix ctm, fz_colorspace *cs, const float *color, float alpha, fz_color_params cp) { unwrap_other((unwrap_device *)dev); }
1916+
static void unwrap_clip_text(fz_context *ctx, fz_device *dev, const fz_text *text, fz_matrix ctm, fz_rect scissor) { unwrap_other((unwrap_device *)dev); }
1917+
static void unwrap_clip_stroke_text(fz_context *ctx, fz_device *dev, const fz_text *text, const fz_stroke_state *ss, fz_matrix ctm, fz_rect scissor) { unwrap_other((unwrap_device *)dev); }
1918+
static void unwrap_fill_shade(fz_context *ctx, fz_device *dev, fz_shade *shd, fz_matrix ctm, float alpha, fz_color_params cp) { unwrap_other((unwrap_device *)dev); }
1919+
static void unwrap_fill_image_mask(fz_context *ctx, fz_device *dev, fz_image *img, fz_matrix ctm, fz_colorspace *cs, const float *color, float alpha, fz_color_params cp) { unwrap_other((unwrap_device *)dev); }
1920+
static void unwrap_clip_image_mask(fz_context *ctx, fz_device *dev, fz_image *img, fz_matrix ctm, fz_rect scissor) { unwrap_other((unwrap_device *)dev); }
1921+
static void unwrap_begin_mask(fz_context *ctx, fz_device *dev, fz_rect area, int luminosity, fz_colorspace *cs, const float *bc, fz_color_params cp) { unwrap_other((unwrap_device *)dev); }
1922+
static void unwrap_begin_group(fz_context *ctx, fz_device *dev, fz_rect area, fz_colorspace *cs, int isolated, int knockout, int blendmode, float alpha) { unwrap_other((unwrap_device *)dev); }
1923+
static int unwrap_begin_tile(fz_context *ctx, fz_device *dev, fz_rect area, fz_rect view, float xstep, float ystep, fz_matrix ctm, int id, int doc_id) { unwrap_other((unwrap_device *)dev); return 0; }
1924+
1925+
static void
1926+
unwrap_drop_device(fz_context *ctx, fz_device *dev_)
1927+
{
1928+
unwrap_device *dev = (unwrap_device *)dev_;
1929+
fz_drop_image(ctx, dev->image);
1930+
}
1931+
1932+
fz_image *
1933+
fz_unwrap_display_list_image(fz_context *ctx, fz_image *image_, fz_matrix *ctm)
1934+
{
1935+
fz_display_list_image *image = (fz_display_list_image *)image_;
1936+
unwrap_device *dev = NULL;
1937+
fz_image *found = NULL;
1938+
1939+
if (!image_ || image_->get_pixmap != display_list_image_get_pixmap)
1940+
return NULL;
1941+
1942+
fz_var(dev);
1943+
fz_try(ctx)
1944+
{
1945+
dev = fz_new_derived_device(ctx, unwrap_device);
1946+
dev->super.drop_device = unwrap_drop_device;
1947+
dev->super.fill_path = unwrap_fill_path;
1948+
dev->super.stroke_path = unwrap_stroke_path;
1949+
dev->super.clip_path = unwrap_clip_path;
1950+
dev->super.clip_stroke_path = unwrap_clip_stroke_path;
1951+
dev->super.fill_text = unwrap_fill_text;
1952+
dev->super.stroke_text = unwrap_stroke_text;
1953+
dev->super.clip_text = unwrap_clip_text;
1954+
dev->super.clip_stroke_text = unwrap_clip_stroke_text;
1955+
dev->super.fill_shade = unwrap_fill_shade;
1956+
dev->super.fill_image = unwrap_fill_image;
1957+
dev->super.fill_image_mask = unwrap_fill_image_mask;
1958+
dev->super.clip_image_mask = unwrap_clip_image_mask;
1959+
dev->super.begin_mask = unwrap_begin_mask;
1960+
dev->super.begin_group = unwrap_begin_group;
1961+
dev->super.begin_tile = unwrap_begin_tile;
1962+
fz_run_display_list(ctx, image->list, &dev->super, fz_identity, fz_infinite_rect, NULL);
1963+
fz_close_device(ctx, &dev->super);
1964+
if (dev->image && !dev->other)
1965+
{
1966+
/* raster unit square -> list coordinates -> this image's unit square */
1967+
*ctm = fz_concat(dev->ctm, image->transform);
1968+
found = fz_keep_image(ctx, dev->image);
1969+
}
1970+
}
1971+
fz_always(ctx)
1972+
fz_drop_device(ctx, &dev->super);
1973+
fz_catch(ctx)
1974+
{
1975+
fz_drop_image(ctx, found);
1976+
fz_rethrow(ctx);
1977+
}
1978+
return found;
1979+
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
fitz: look through a display-list image that is one raster (#6330)
2+
3+
An SVG around a single <image> (a fixed-layout EPUB page) becomes a
4+
display-list image, so Save Image and copy got a re-rasterized bitmap
5+
at the layout size. fz_unwrap_display_list_image returns that raster
6+
and the matrix from its unit square to the display-list image's, so the
7+
app can save the original bytes at full resolution.
8+
9+
diff --git a/include/mupdf/fitz/display-list.h b/include/mupdf/fitz/display-list.h
10+
index 957efe4c2..c8e92e656 100644
11+
--- a/include/mupdf/fitz/display-list.h
12+
+++ b/include/mupdf/fitz/display-list.h
13+
@@ -130,6 +130,12 @@ fz_rect fz_bound_display_list(fz_context *ctx, fz_display_list *list);
14+
*/
15+
fz_image *fz_new_image_from_display_list(fz_context *ctx, float w, float h, fz_display_list *list);
16+
17+
+/**
18+
+ SumatraPDF: the single raster a display-list image draws, or NULL.
19+
+ ctm maps the raster's unit square to the display-list image's.
20+
+*/
21+
+fz_image *fz_unwrap_display_list_image(fz_context *ctx, fz_image *image, fz_matrix *ctm);
22+
+
23+
/**
24+
Check for a display list being empty
25+
26+
diff --git a/source/fitz/image.c b/source/fitz/image.c
27+
index a66a7d49d..6a16d5175 100644
28+
--- a/source/fitz/image.c
29+
+++ b/source/fitz/image.c
30+
@@ -1876,3 +1876,104 @@ fz_image *fz_new_image_from_display_list(fz_context *ctx, float w, float h, fz_d
31+
32+
return &image->super;
33+
}
34+
+
35+
+/* SumatraPDF: a display-list image (an SVG) that is one raster and nothing
36+
+ * else. Save Image then keeps the raster's bytes and resolution (#6330). */
37+
+typedef struct
38+
+{
39+
+ fz_device super;
40+
+ fz_image *image;
41+
+ fz_matrix ctm;
42+
+ int images;
43+
+ int other;
44+
+} unwrap_device;
45+
+
46+
+static void
47+
+unwrap_fill_image(fz_context *ctx, fz_device *dev_, fz_image *img, fz_matrix ctm, float alpha, fz_color_params color_params)
48+
+{
49+
+ unwrap_device *dev = (unwrap_device *)dev_;
50+
+ if (dev->images++ > 0 || alpha != 1)
51+
+ {
52+
+ dev->other = 1;
53+
+ return;
54+
+ }
55+
+ dev->image = fz_keep_image(ctx, img);
56+
+ dev->ctm = ctm;
57+
+}
58+
+
59+
+static void
60+
+unwrap_other(unwrap_device *dev)
61+
+{
62+
+ dev->other = 1;
63+
+}
64+
+
65+
+static void unwrap_fill_path(fz_context *ctx, fz_device *dev, const fz_path *path, int even_odd, fz_matrix ctm, fz_colorspace *cs, const float *color, float alpha, fz_color_params cp) { unwrap_other((unwrap_device *)dev); }
66+
+static void unwrap_stroke_path(fz_context *ctx, fz_device *dev, const fz_path *path, const fz_stroke_state *ss, fz_matrix ctm, fz_colorspace *cs, const float *color, float alpha, fz_color_params cp) { unwrap_other((unwrap_device *)dev); }
67+
+static void unwrap_clip_path(fz_context *ctx, fz_device *dev, const fz_path *path, int even_odd, fz_matrix ctm, fz_rect scissor) { unwrap_other((unwrap_device *)dev); }
68+
+static void unwrap_clip_stroke_path(fz_context *ctx, fz_device *dev, const fz_path *path, const fz_stroke_state *ss, fz_matrix ctm, fz_rect scissor) { unwrap_other((unwrap_device *)dev); }
69+
+static void unwrap_fill_text(fz_context *ctx, fz_device *dev, const fz_text *text, fz_matrix ctm, fz_colorspace *cs, const float *color, float alpha, fz_color_params cp) { unwrap_other((unwrap_device *)dev); }
70+
+static void unwrap_stroke_text(fz_context *ctx, fz_device *dev, const fz_text *text, const fz_stroke_state *ss, fz_matrix ctm, fz_colorspace *cs, const float *color, float alpha, fz_color_params cp) { unwrap_other((unwrap_device *)dev); }
71+
+static void unwrap_clip_text(fz_context *ctx, fz_device *dev, const fz_text *text, fz_matrix ctm, fz_rect scissor) { unwrap_other((unwrap_device *)dev); }
72+
+static void unwrap_clip_stroke_text(fz_context *ctx, fz_device *dev, const fz_text *text, const fz_stroke_state *ss, fz_matrix ctm, fz_rect scissor) { unwrap_other((unwrap_device *)dev); }
73+
+static void unwrap_fill_shade(fz_context *ctx, fz_device *dev, fz_shade *shd, fz_matrix ctm, float alpha, fz_color_params cp) { unwrap_other((unwrap_device *)dev); }
74+
+static void unwrap_fill_image_mask(fz_context *ctx, fz_device *dev, fz_image *img, fz_matrix ctm, fz_colorspace *cs, const float *color, float alpha, fz_color_params cp) { unwrap_other((unwrap_device *)dev); }
75+
+static void unwrap_clip_image_mask(fz_context *ctx, fz_device *dev, fz_image *img, fz_matrix ctm, fz_rect scissor) { unwrap_other((unwrap_device *)dev); }
76+
+static void unwrap_begin_mask(fz_context *ctx, fz_device *dev, fz_rect area, int luminosity, fz_colorspace *cs, const float *bc, fz_color_params cp) { unwrap_other((unwrap_device *)dev); }
77+
+static void unwrap_begin_group(fz_context *ctx, fz_device *dev, fz_rect area, fz_colorspace *cs, int isolated, int knockout, int blendmode, float alpha) { unwrap_other((unwrap_device *)dev); }
78+
+static int unwrap_begin_tile(fz_context *ctx, fz_device *dev, fz_rect area, fz_rect view, float xstep, float ystep, fz_matrix ctm, int id, int doc_id) { unwrap_other((unwrap_device *)dev); return 0; }
79+
+
80+
+static void
81+
+unwrap_drop_device(fz_context *ctx, fz_device *dev_)
82+
+{
83+
+ unwrap_device *dev = (unwrap_device *)dev_;
84+
+ fz_drop_image(ctx, dev->image);
85+
+}
86+
+
87+
+fz_image *
88+
+fz_unwrap_display_list_image(fz_context *ctx, fz_image *image_, fz_matrix *ctm)
89+
+{
90+
+ fz_display_list_image *image = (fz_display_list_image *)image_;
91+
+ unwrap_device *dev = NULL;
92+
+ fz_image *found = NULL;
93+
+
94+
+ if (!image_ || image_->get_pixmap != display_list_image_get_pixmap)
95+
+ return NULL;
96+
+
97+
+ fz_var(dev);
98+
+ fz_try(ctx)
99+
+ {
100+
+ dev = fz_new_derived_device(ctx, unwrap_device);
101+
+ dev->super.drop_device = unwrap_drop_device;
102+
+ dev->super.fill_path = unwrap_fill_path;
103+
+ dev->super.stroke_path = unwrap_stroke_path;
104+
+ dev->super.clip_path = unwrap_clip_path;
105+
+ dev->super.clip_stroke_path = unwrap_clip_stroke_path;
106+
+ dev->super.fill_text = unwrap_fill_text;
107+
+ dev->super.stroke_text = unwrap_stroke_text;
108+
+ dev->super.clip_text = unwrap_clip_text;
109+
+ dev->super.clip_stroke_text = unwrap_clip_stroke_text;
110+
+ dev->super.fill_shade = unwrap_fill_shade;
111+
+ dev->super.fill_image = unwrap_fill_image;
112+
+ dev->super.fill_image_mask = unwrap_fill_image_mask;
113+
+ dev->super.clip_image_mask = unwrap_clip_image_mask;
114+
+ dev->super.begin_mask = unwrap_begin_mask;
115+
+ dev->super.begin_group = unwrap_begin_group;
116+
+ dev->super.begin_tile = unwrap_begin_tile;
117+
+ fz_run_display_list(ctx, image->list, &dev->super, fz_identity, fz_infinite_rect, NULL);
118+
+ fz_close_device(ctx, &dev->super);
119+
+ if (dev->image && !dev->other)
120+
+ {
121+
+ /* raster unit square -> list coordinates -> this image's unit square */
122+
+ *ctm = fz_concat(dev->ctm, image->transform);
123+
+ found = fz_keep_image(ctx, dev->image);
124+
+ }
125+
+ }
126+
+ fz_always(ctx)
127+
+ fz_drop_device(ctx, &dev->super);
128+
+ fz_catch(ctx)
129+
+ {
130+
+ fz_drop_image(ctx, found);
131+
+ fz_rethrow(ctx);
132+
+ }
133+
+ return found;
134+
+}

‎ext/patches/README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ relative to `ext/mupdf`, so `-p1` from inside that directory.
5858
| `0055-svg-data-uri-parameters` | SVG image data URIs accept media-type parameters before `;base64` (#6322) |
5959
| `0056-html-svg-percent-size` | SVG percentage dimensions resolve against the HTML page viewport (#6324) |
6060
| `0057-scalable-image-device-size` | SVG / display-list images rasterize at the zoomed size instead of a fixed 96 dpi; fixed-layout EPUB pages were blurry (#6330) |
61+
| `0058-unwrap-display-list-image` | `fz_unwrap_display_list_image`: the one raster an SVG draws, so Save Image / copy keep its bytes and resolution (#6330) |
6162

6263
That is the whole list: `ext/mupdf` is byte-for-byte `1.28.5` plus these
6364
patches, and nothing else.

‎src/SumatraTest.cpp‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1010,6 +1010,9 @@ TempStr ImageOrientationResultTemp(Str pdfPath, int pageNo, Str bmpPath, int* ex
10101010
return fail(StrL("ERROR no-image-element"));
10111011
}
10121012
RenderedBitmap* bmp = engine->GetImageForPageElement(imgEl);
1013+
Str origData = engine->GetImageDataForPageElement(imgEl);
1014+
int dataLen = len(origData);
1015+
str::Free(origData);
10131016
SafeEngineRelease(&engine);
10141017
if (!bmp) {
10151018
return fail(StrL("ERROR no-image"));
@@ -1021,7 +1024,7 @@ TempStr ImageOrientationResultTemp(Str pdfPath, int pageNo, Str bmpPath, int* ex
10211024
}
10221025
int w = px->width;
10231026
int h = px->height;
1024-
out.Append(fmt("w=%d h=%d tl=%s tr=%s bl=%s br=%s\n", w, h, PixmapRgbHexTemp(px, 0, 0),
1027+
out.Append(fmt("w=%d h=%d data=%d tl=%s tr=%s bl=%s br=%s\n", w, h, dataLen, PixmapRgbHexTemp(px, 0, 0),
10251028
PixmapRgbHexTemp(px, w - 1, 0), PixmapRgbHexTemp(px, 0, h - 1), PixmapRgbHexTemp(px, w - 1, h - 1)));
10261029
if (len(bmpPath) > 0) {
10271030
SavePixmapAsBmp(px, bmpPath);

‎src/libsumatrapdf.def‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,8 @@ EXPORTS
404404

405405
fz_compressed_image_buffer
406406

407+
fz_unwrap_display_list_image
408+
407409
fz_open_compressed_buffer
408410

409411
fz_open_image_decomp_stream_from_buffer

‎src/ng/SumatraControl.cpp‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1326,6 +1326,9 @@ TempStr ImageOrientationResultTemp(Str pdfPath, int pageNo, int* exitCodeOut) {
13261326
Pixmap* px = nullptr;
13271327
#if OS_WIN
13281328
RenderedBitmap* bmp = engine->GetImageForPageElement(imgEl);
1329+
Str origData = engine->GetImageDataForPageElement(imgEl);
1330+
int dataLen = len(origData);
1331+
str::Free(origData);
13291332
SafeEngineRelease(&engine);
13301333
if (!bmp) {
13311334
return fail(StrL("ERROR no-image"));
@@ -1344,7 +1347,7 @@ TempStr ImageOrientationResultTemp(Str pdfPath, int pageNo, int* exitCodeOut) {
13441347
}
13451348
int w = px->width;
13461349
int h = px->height;
1347-
out.Append(fmt("w=%d h=%d tl=%s tr=%s bl=%s br=%s\n", w, h, PixmapRgbHexTemp(px, 0, 0),
1350+
out.Append(fmt("w=%d h=%d data=%d tl=%s tr=%s bl=%s br=%s\n", w, h, dataLen, PixmapRgbHexTemp(px, 0, 0),
13481351
PixmapRgbHexTemp(px, w - 1, 0), PixmapRgbHexTemp(px, 0, h - 1), PixmapRgbHexTemp(px, w - 1, h - 1)));
13491352
FreePixmap(px);
13501353
if (exitCodeOut) {

‎src/shared/EngineMupdf.cpp‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2167,8 +2167,17 @@ static fz_image* FzFindImageAtIdx(fz_context* ctx, FzPageInfo* pageInfo, int idx
21672167
// Same skip as FzFindImagePositions, so imageID stays aligned.
21682168
if (idx == 0) {
21692169
image = fz_keep_image(ctx, image);
2170+
fz_matrix ctm = block->u.i.transform;
2171+
// an svg around one raster: save and copy the raster itself
2172+
fz_matrix inner = fz_identity;
2173+
fz_image* raster = fz_unwrap_display_list_image(ctx, image, &inner);
2174+
if (raster) {
2175+
fz_drop_image(ctx, image);
2176+
image = raster;
2177+
ctm = fz_concat(inner, ctm);
2178+
}
21702179
if (ctmOut) {
2171-
*ctmOut = block->u.i.transform;
2180+
*ctmOut = ctm;
21722181
}
21732182
fz_drop_stext_page(ctx, stext);
21742183
return image;

‎tests/issue-6330.ts‎

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@
55
// size, so zooming in scaled up a small bitmap and the page was blurry.
66
//
77
// The page is a 300x400 pt svg holding a 600x800 px image of 1 px black and
8-
// white stripes. At 200% a sharp render is 1:1 black and white, while the old
9-
// 400x533 raster scaled up is gray.
8+
// white stripes under a solid black top band. At 200% a sharp render is 1:1
9+
// black and white, while the old 400x533 raster scaled up is gray.
10+
//
11+
// Save Image / copy also look through the svg: the bitmap is the raster at
12+
// its own 600x800, upright, and the original PNG bytes are available.
1013
//
1114
// Run: bun tests/issue-6330.ts [--no-build]
1215

@@ -21,8 +24,10 @@ const SVG_W = 300;
2124
const SVG_H = 400;
2225
const IMG_W = (SVG_W * ZOOM_PERCENT) / 100;
2326
const IMG_H = (SVG_H * ZOOM_PERCENT) / 100;
27+
const BAND_H = 100;
2428

25-
// 8-bit gray PNG, columns alternate black and white
29+
// 8-bit gray PNG: a black band on top, below it columns alternate black and
30+
// white, so the corners tell a flipped bitmap from an upright one
2631
function makeStripesPng(w: number, h: number): Buffer {
2732
const ihdr = Buffer.alloc(13);
2833
ihdr.writeUInt32BE(w, 0);
@@ -35,7 +40,7 @@ function makeStripesPng(w: number, h: number): Buffer {
3540
const row = y * stride;
3641
raw[row] = 0;
3742
for (let x = 0; x < w; x++) {
38-
raw[row + 1 + x] = x & 1 ? 255 : 0;
43+
raw[row + 1 + x] = y >= BAND_H && x & 1 ? 255 : 0;
3944
}
4045
}
4146
const sig = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
@@ -124,6 +129,28 @@ export async function testit(): Promise<void> {
124129
if (page.gray > (boxW * boxH) / 10) {
125130
throw new Error(`issue-6330: the page is blurry: ${page.raw}`);
126131
}
132+
133+
const res = await client.request(ControlCommand.TestImageOrientation, [epubPath, 1]);
134+
const raw = String(res[1] ?? "").trim();
135+
if (res[0] !== 0) {
136+
throw new Error(`issue-6330: image element failed: ${raw}`);
137+
}
138+
const m = /w=(\d+) h=(\d+) data=(\d+) tl=(\w+) tr=(\w+) bl=(\w+) br=(\w+)/.exec(raw);
139+
if (!m) {
140+
throw new Error(`issue-6330: unexpected image result: ${raw}`);
141+
}
142+
console.log(`issue-6330: image ${raw}`);
143+
const [, w, h, data, tl, tr, bl, br] = m;
144+
if (Number(w) !== IMG_W || Number(h) !== IMG_H) {
145+
throw new Error(`issue-6330: want the ${IMG_W}x${IMG_H} raster, not the svg render: ${raw}`);
146+
}
147+
if (Number(data) === 0) {
148+
throw new Error(`issue-6330: the original image bytes are not available: ${raw}`);
149+
}
150+
// black band on top, bottom-right is the last (white) stripe column
151+
if (tl !== "000000" || tr !== "000000" || bl !== "000000" || br !== "ffffff") {
152+
throw new Error(`issue-6330: the raster is not upright: ${raw}`);
153+
}
127154
});
128155
}
129156

0 commit comments

Comments
 (0)