diff options
author | Robin Watts <robin.watts@artifex.com> | 2016-04-07 11:08:42 +0100 |
---|---|---|
committer | Robin Watts <robin.watts@artifex.com> | 2016-04-28 12:30:40 +0100 |
commit | b944f16a564f8a31d9a064980318ad690be31f8e (patch) | |
tree | 6fb5a572487943e7cdccee2b201ae526c23bd595 /source/fitz | |
parent | 6ab232064f3fc9037e97157dd9a77f286ad85e23 (diff) | |
download | mupdf-b944f16a564f8a31d9a064980318ad690be31f8e.tar.xz |
Partial image decode.
Update the core fz_get_pixmap_from_image code to allow fetching
a subarea of a pixmap. We pass in the required subarea, together
with the transformation matrix for the whole image.
On return, we have a pixmap at least as big as was requested,
and the transformation matrix is updated to map the supplied
area to the correct place on the screen.
The draw device is updated to use this as required. Everywhere
else passes NULLs in, and so gets unchanged behaviour.
The standard 'get_pixmap' function has been updated to decode
just the required areas of the bitmaps.
This means that banded rendering of pages will decode just the
image subareas that are required for each band, limiting the
memory use. The downside to this is that each band will redecode
the image again to extract just the section we want.
The image subareas are put into the fz_store in the same way
as full images. Currently image areas in the store are only
matched when they match exactly; subareas are not identified
as being able to use existing images.
Diffstat (limited to 'source/fitz')
-rw-r--r-- | source/fitz/draw-device.c | 88 | ||||
-rw-r--r-- | source/fitz/geometry.c | 12 | ||||
-rw-r--r-- | source/fitz/image.c | 209 | ||||
-rw-r--r-- | source/fitz/pixmap.c | 4 | ||||
-rw-r--r-- | source/fitz/stext-output.c | 2 | ||||
-rw-r--r-- | source/fitz/stream-read.c | 20 | ||||
-rw-r--r-- | source/fitz/svg-device.c | 6 | ||||
-rw-r--r-- | source/fitz/test-device.c | 2 |
8 files changed, 307 insertions, 36 deletions
diff --git a/source/fitz/draw-device.c b/source/fitz/draw-device.c index b9db1558..4108cf09 100644 --- a/source/fitz/draw-device.c +++ b/source/fitz/draw-device.c @@ -1106,6 +1106,8 @@ fz_draw_fill_image(fz_context *ctx, fz_device *devp, fz_image *image, const fz_m fz_colorspace *model = state->dest->colorspace; fz_irect clip; fz_matrix local_ctm = *ctm; + fz_matrix inverse; + fz_irect src_area; fz_intersect_irect(fz_pixmap_bbox(ctx, state->dest, &clip), &state->scissor); @@ -1120,10 +1122,42 @@ fz_draw_fill_image(fz_context *ctx, fz_device *devp, fz_image *image, const fz_m if (image->w == 0 || image->h == 0) return; - dx = sqrtf(local_ctm.a * local_ctm.a + local_ctm.b * local_ctm.b); - dy = sqrtf(local_ctm.c * local_ctm.c + local_ctm.d * local_ctm.d); - - pixmap = fz_get_pixmap_from_image(ctx, image, dx, dy); + /* ctm maps the image (expressed as the unit square) onto the + * destination device. Reverse that to get a mapping from + * the destination device to the source pixels. */ + if (fz_try_invert_matrix(&inverse, &local_ctm)) + { + /* Not invertible. Could just bale? Use the whole image + * for now. */ + src_area.x0 = 0; + src_area.x1 = image->w; + src_area.y0 = 0; + src_area.y1 = image->h; + } + else + { + float exp; + fz_rect rect; + fz_irect sane; + /* We want to scale from image coords, not from unit square */ + fz_post_scale(&inverse, image->w, image->h); + /* Are we scaling up or down? exp < 1 means scaling down. */ + exp = fz_matrix_max_expansion(&inverse); + fz_rect_from_irect(&rect, &clip); + fz_transform_rect(&rect, &inverse); + /* Allow for support requirements for scalers. */ + fz_expand_rect(&rect, fz_max(exp, 1) * 4); + fz_irect_from_rect(&src_area, &rect); + sane.x0 = 0; + sane.y0 = 0; + sane.x1 = image->w; + sane.y1 = image->h; + fz_intersect_irect(&src_area, &sane); + if (fz_is_empty_irect(&src_area)) + return; + } + + pixmap = fz_get_pixmap_from_image(ctx, image, &src_area, &local_ctm, &dx, &dy); orig_pixmap = pixmap; /* convert images with more components (cmyk->rgb) before scaling */ @@ -1214,6 +1248,8 @@ fz_draw_fill_image_mask(fz_context *ctx, fz_device *devp, fz_image *image, const fz_colorspace *model = state->dest->colorspace; fz_irect clip; fz_matrix local_ctm = *ctm; + fz_matrix inverse; + fz_irect src_area; fz_pixmap_bbox(ctx, state->dest, &clip); fz_intersect_irect(&clip, &state->scissor); @@ -1221,9 +1257,42 @@ fz_draw_fill_image_mask(fz_context *ctx, fz_device *devp, fz_image *image, const if (image->w == 0 || image->h == 0) return; - dx = sqrtf(local_ctm.a * local_ctm.a + local_ctm.b * local_ctm.b); - dy = sqrtf(local_ctm.c * local_ctm.c + local_ctm.d * local_ctm.d); - pixmap = fz_get_pixmap_from_image(ctx, image, dx, dy); + /* ctm maps the image (expressed as the unit square) onto the + * destination device. Reverse that to get a mapping from + * the destination device to the source pixels. */ + if (fz_try_invert_matrix(&inverse, &local_ctm)) + { + /* Not invertible. Could just bale? Use the whole image + * for now. */ + src_area.x0 = 0; + src_area.x1 = image->w; + src_area.y0 = 0; + src_area.y1 = image->h; + } + else + { + float exp; + fz_rect rect; + fz_irect sane; + /* We want to scale from image coords, not from unit square */ + fz_post_scale(&inverse, image->w, image->h); + /* Are we scaling up or down? exp < 1 means scaling down. */ + exp = fz_matrix_max_expansion(&inverse); + fz_rect_from_irect(&rect, &clip); + fz_transform_rect(&rect, &inverse); + /* Allow for support requirements for scalers. */ + fz_expand_rect(&rect, fz_max(exp, 1) * 4); + fz_irect_from_rect(&src_area, &rect); + sane.x0 = 0; + sane.y0 = 0; + sane.x1 = image->w; + sane.y1 = image->h; + fz_intersect_irect(&src_area, &sane); + if (fz_is_empty_irect(&src_area)) + return; + } + + pixmap = fz_get_pixmap_from_image(ctx, image, &src_area, &local_ctm, &dx, &dy); orig_pixmap = pixmap; fz_try(ctx) @@ -1321,12 +1390,9 @@ fz_draw_clip_image_mask(fz_context *ctx, fz_device *devp, fz_image *image, const fz_intersect_irect(&bbox, fz_irect_from_rect(&bbox2, scissor)); } - dx = sqrtf(local_ctm.a * local_ctm.a + local_ctm.b * local_ctm.b); - dy = sqrtf(local_ctm.c * local_ctm.c + local_ctm.d * local_ctm.d); - fz_try(ctx) { - pixmap = fz_get_pixmap_from_image(ctx, image, dx, dy); + pixmap = fz_get_pixmap_from_image(ctx, image, NULL, &local_ctm, &dx, &dy); orig_pixmap = pixmap; state[1].mask = mask = fz_new_pixmap_with_bbox(ctx, NULL, &bbox); diff --git a/source/fitz/geometry.c b/source/fitz/geometry.c index 073536ff..f7a92816 100644 --- a/source/fitz/geometry.c +++ b/source/fitz/geometry.c @@ -59,6 +59,18 @@ fz_pre_scale(fz_matrix *mat, float sx, float sy) } fz_matrix * +fz_post_scale(fz_matrix *mat, float sx, float sy) +{ + mat->a *= sx; + mat->b *= sy; + mat->c *= sx; + mat->d *= sy; + mat->e *= sx; + mat->f *= sy; + return mat; +} + +fz_matrix * fz_shear(fz_matrix *mat, float h, float v) { mat->a = 1; mat->b = v; diff --git a/source/fitz/image.c b/source/fitz/image.c index 9c66176f..36026a19 100644 --- a/source/fitz/image.c +++ b/source/fitz/image.c @@ -21,14 +21,16 @@ struct fz_image_key_s { int refs; fz_image *image; int l2factor; + fz_irect rect; }; static int fz_make_hash_image_key(fz_context *ctx, fz_store_hash *hash, void *key_) { fz_image_key *key = (fz_image_key *)key_; - hash->u.pi.ptr = key->image; - hash->u.pi.i = key->l2factor; + hash->u.pir.ptr = key->image; + hash->u.pir.i = key->l2factor; + hash->u.pir.r = key->rect; return 1; } @@ -55,7 +57,7 @@ fz_cmp_image_key(fz_context *ctx, void *k0_, void *k1_) { fz_image_key *k0 = (fz_image_key *)k0_; fz_image_key *k1 = (fz_image_key *)k1_; - return k0->image == k1->image && k0->l2factor == k1->l2factor; + return k0->image == k1->image && k0->l2factor == k1->l2factor && k0->rect.x0 == k1->rect.x0 && k0->rect.y0 == k1->rect.y0 && k0->rect.x1 == k1->rect.x1 && k0->rect.y1 == k1->rect.y1; } static void @@ -96,7 +98,7 @@ fz_mask_color_key(fz_pixmap *pix, int n, const int *colorkey) static void fz_unblend_masked_tile(fz_context *ctx, fz_pixmap *tile, fz_image *image) { - fz_pixmap *mask = fz_get_pixmap_from_image(ctx, image->mask, tile->w, tile->h); + fz_pixmap *mask = fz_get_pixmap_from_image(ctx, image->mask, NULL, NULL, NULL, NULL); unsigned char *s = mask->samples, *end = s + mask->w * mask->h; unsigned char *d = tile->samples; int k; @@ -122,14 +124,39 @@ fz_unblend_masked_tile(fz_context *ctx, fz_pixmap *tile, fz_image *image) } fz_pixmap * -fz_decomp_image_from_stream(fz_context *ctx, fz_stream *stm, fz_image *image, int indexed, int l2factor) +fz_decomp_image_from_stream(fz_context *ctx, fz_stream *stm, fz_image *image, fz_irect *subarea, int indexed, int l2factor) { fz_pixmap *tile = NULL; int stride, len, i; unsigned char *samples = NULL; int f = 1<<l2factor; - int w = (image->w + f-1) >> l2factor; - int h = (image->h + f-1) >> l2factor; + int w = image->w; + int h = image->h; + + if (subarea) + { + int bpp = image->bpc * image->n; + int mask; + switch (bpp) + { + case 1: mask = 8*f; break; + case 2: mask = 4*f; break; + case 4: mask = 2*f; break; + default: mask = f; break; + } + subarea->x0 &= ~(mask - 1); + subarea->y0 &= ~(f - 1); + subarea->x1 = (subarea->x1 + mask - 1) & ~(mask - 1); + if (subarea->x1 > image->w) + subarea->x1 = image->w; + subarea->y1 = (subarea->y1 + f - 1) & ~(f - 1); + if (subarea->y1 > image->h) + subarea->y1 = image->h; + w = (subarea->x1 - subarea->x0); + h = (subarea->y1 - subarea->y0); + } + w = (w + f - 1) >> l2factor; + h = (h + f - 1) >> l2factor; fz_var(tile); fz_var(samples); @@ -143,7 +170,46 @@ fz_decomp_image_from_stream(fz_context *ctx, fz_stream *stm, fz_image *image, in samples = fz_malloc_array(ctx, h, stride); - len = fz_read(ctx, stm, samples, h * stride); + if (subarea) + { + int hh; + unsigned char *s = samples; + int stream_w = (image->w + f - 1)>>l2factor; + int stream_stride = (stream_w * image->n * image->bpc + 7) / 8; + int l_margin = subarea->x0 >> l2factor; + int t_margin = subarea->y0 >> l2factor; + int r_margin = (image->w + f - 1 - subarea->x1) >> l2factor; + int b_margin = (image->h + f - 1 - subarea->y1) >> l2factor; + int l_skip = (l_margin * image->n * image->bpc)/8; + int r_skip = (r_margin * image->n * image->bpc)/8; + int t_skip = t_margin * stream_stride + l_skip; + int b_skip = b_margin * stream_stride + r_skip; + int l = fz_skip(ctx, stm, t_skip); + len = 0; + if (l == t_skip) + { + hh = h; + do + { + l = fz_read(ctx, stm, s, stride); + s += l; + len += l; + if (l < stride) + break; + if (--hh == 0) + break; + l = fz_skip(ctx, stm, r_skip + l_skip); + if (l < r_skip + l_skip) + break; + } + while (1); + (void)fz_skip(ctx, stm, r_skip + b_skip); + } + } + else + { + len = fz_read(ctx, stm, samples, h * stride); + } /* Pad truncated images */ if (len < stride * h) @@ -219,12 +285,13 @@ fz_drop_image_imp(fz_context *ctx, fz_storable *image_) } static fz_pixmap * -standard_image_get_pixmap(fz_context *ctx, fz_image *image, int w, int h, int *l2factor) +standard_image_get_pixmap(fz_context *ctx, fz_image *image, fz_irect *subarea, int w, int h, int *l2factor) { int native_l2factor; fz_stream *stm; int indexed; fz_pixmap *tile; + int can_sub = 0; /* We need to make a new one. */ /* First check for ones that we can't decode using streams */ @@ -271,7 +338,8 @@ standard_image_get_pixmap(fz_context *ctx, fz_image *image, int w, int h, int *l native_l2factor -= *l2factor; indexed = fz_colorspace_is_indexed(ctx, image->colorspace); - tile = fz_decomp_image_from_stream(ctx, stm, image, indexed, native_l2factor); + can_sub = 1; + tile = fz_decomp_image_from_stream(ctx, stm, image, subarea, indexed, native_l2factor); /* CMYK JPEGs in XPS documents have to be inverted */ if (image->invert_cmyk_jpeg && @@ -285,20 +353,63 @@ standard_image_get_pixmap(fz_context *ctx, fz_image *image, int w, int h, int *l break; } + if (can_sub == 0 && subarea != NULL) + { + subarea->x0 = 0; + subarea->y0 = 0; + subarea->x1 = image->w; + subarea->y1 = image->h; + } + return tile; } +static void +update_ctm_for_subarea(fz_matrix *ctm, const fz_irect *subarea, int w, int h) +{ + fz_matrix m; + + if (subarea->x0 == 0 && subarea->y0 == 0 && subarea->x1 == w && subarea->y1 == h) + return; + + m.a = (subarea->x1 - subarea->x0) / (float)w; + m.b = 0; + m.c = 0; + m.d = (subarea->y1 - subarea->y0) / (float)h; + m.e = subarea->x0 / (float)w; + m.f = subarea->y0 / (float)h; + fz_concat(ctm, &m, ctm); +} + fz_pixmap * -fz_get_pixmap_from_image(fz_context *ctx, fz_image *image, int w, int h) +fz_get_pixmap_from_image(fz_context *ctx, fz_image *image, const fz_irect *subarea, fz_matrix *ctm, int *dw, int *dh) { fz_pixmap *tile; int l2factor, l2factor_remaining; fz_image_key key; fz_image_key *keyp; + int w; + int h; if (!image) return NULL; + /* Figure out the extent. */ + if (ctm) + { + w = sqrtf(ctm->a * ctm->a + ctm->b * ctm->b); + if (w > image->w) + w = image->w; + h = sqrtf(ctm->c * ctm->c + ctm->d * ctm->d); + if (h > image->h) + w = image->h; + } + else + { + w = image->w; + h = image->h; + } + /* 'Simple' images created direct from pixmaps will have no buffer * of compressed data. We cannot do any better than just returning * a pointer to the original 'tile'. @@ -307,13 +418,13 @@ fz_get_pixmap_from_image(fz_context *ctx, fz_image *image, int w, int h) * with masks applied, so we need both parts of the following test. */ if (image->buffer == NULL && image->tile != NULL) + { + if (dw) + *dw = w; + if (dh) + *dh = h; return fz_keep_pixmap(ctx, image->tile); /* That's all we can give you! */ - - /* Ensure our expectations for tile size are reasonable */ - if (w < 0 || w > image->w) - w = image->w; - if (h < 0 || h > image->h) - h = image->h; + } /* What is our ideal factor? We search for the largest factor where * we can subdivide and stay larger than the required size. We add @@ -324,6 +435,61 @@ fz_get_pixmap_from_image(fz_context *ctx, fz_image *image, int w, int h) else for (l2factor=0; image->w>>(l2factor+1) >= w+2 && image->h>>(l2factor+1) >= h+2 && l2factor < 6; l2factor++); + /* Now figure out if we want to decode just a subarea */ + if (subarea == NULL || (subarea->x1-subarea->x0)*(subarea->y1-subarea->y0) >= (image->w*image->h/10)*9) + { + /* Either no subarea specified, or a subarea 90% or more of the + * whole area specified. Use the whole image. */ + key.rect.x0 = 0; + key.rect.y0 = 0; + key.rect.x1 = image->w; + key.rect.y1 = image->h; + } + else + { + /* Clip to the edges if they are within 1% */ + key.rect = *subarea; + if (key.rect.x0 <= image->w/100) + key.rect.x0 = 0; + if (key.rect.y0 <= image->h/100) + key.rect.y0 = 0; + if (key.rect.x1 >= image->w*99/100) + key.rect.x1 = image->w; + if (key.rect.y1 >= image->h*99/100) + key.rect.y1 = image->h; + } + + if (ctm) + { + float frac_w = (key.rect.x1 - key.rect.x0) / (float)image->w; + float frac_h = (key.rect.y1 - key.rect.y0) / (float)image->h; + float a = ctm->a * frac_w; + float b = ctm->b * frac_h; + float c = ctm->c * frac_w; + float d = ctm->d * frac_h; + + w = sqrtf(a * a + b * b); + h = sqrtf(c * c + d * d); + } + else + { + w = image->w; + h = image->h; + } + + /* Return the true sizes to the caller */ + if (dw) + *dw = w; + if (dh) + *dh = h; + if (w > image->w) + w = image->w; + if (h > image->h) + h = image->h; + + if (w == 0 || h == 0) + l2factor = 0; + /* Can we find any suitable tiles in the cache? */ key.refs = 1; key.image = image; @@ -332,7 +498,10 @@ fz_get_pixmap_from_image(fz_context *ctx, fz_image *image, int w, int h) { tile = fz_find_item(ctx, fz_drop_pixmap_imp, &key, &fz_image_store_type); if (tile) + { + update_ctm_for_subarea(ctm, &key.rect, image->w, image->h); return tile; + } key.l2factor--; } while (key.l2factor >= 0); @@ -340,7 +509,10 @@ fz_get_pixmap_from_image(fz_context *ctx, fz_image *image, int w, int h) /* We'll have to decode the image; request the correct amount of * downscaling. */ l2factor_remaining = l2factor; - tile = image->get_pixmap(ctx, image, w, h, &l2factor_remaining); + tile = image->get_pixmap(ctx, image, &key.rect, w, h, &l2factor_remaining); + + /* Update the ctm to allow for subareas. */ + update_ctm_for_subarea(ctm, &key.rect, image->w, image->h); /* l2factor_remaining is updated to the amount of subscaling left to do */ assert(l2factor_remaining >= 0 && l2factor_remaining <= 6); @@ -360,6 +532,7 @@ fz_get_pixmap_from_image(fz_context *ctx, fz_image *image, int w, int h) keyp->refs = 1; keyp->image = fz_keep_image(ctx, image); keyp->l2factor = l2factor; + keyp->rect = key.rect; existing_tile = fz_store_item(ctx, keyp, tile, fz_pixmap_size(ctx, tile), &fz_image_store_type); if (existing_tile) { diff --git a/source/fitz/pixmap.c b/source/fitz/pixmap.c index 88e16d9d..fca6f733 100644 --- a/source/fitz/pixmap.c +++ b/source/fitz/pixmap.c @@ -1123,9 +1123,9 @@ png_from_pixmap(fz_context *ctx, fz_pixmap *pix, int drop) } fz_buffer * -fz_new_buffer_from_image_as_png(fz_context *ctx, fz_image *image, int w, int h) +fz_new_buffer_from_image_as_png(fz_context *ctx, fz_image *image) { - return png_from_pixmap(ctx, fz_get_pixmap_from_image(ctx, image, image->w, image->h), 1); + return png_from_pixmap(ctx, fz_get_pixmap_from_image(ctx, image, NULL, NULL, NULL, NULL), 1); } fz_buffer * diff --git a/source/fitz/stext-output.c b/source/fitz/stext-output.c index 1ba9fe51..70b0bf7e 100644 --- a/source/fitz/stext-output.c +++ b/source/fitz/stext-output.c @@ -258,7 +258,7 @@ fz_print_stext_page_html(fz_context *ctx, fz_output *out, fz_stext_page *page) break; default: { - fz_buffer *buf = fz_new_buffer_from_image_as_png(ctx, image->image, image->image->w, image->image->h); + fz_buffer *buf = fz_new_buffer_from_image_as_png(ctx, image->image); fz_printf(ctx, out, "image/png;base64,"); send_data_base64(ctx, out, buf); fz_drop_buffer(ctx, buf); diff --git a/source/fitz/stream-read.c b/source/fitz/stream-read.c index 2ebd5ad6..2ace7b1f 100644 --- a/source/fitz/stream-read.c +++ b/source/fitz/stream-read.c @@ -27,6 +27,26 @@ fz_read(fz_context *ctx, fz_stream *stm, unsigned char *buf, int len) return count; } +static unsigned char skip_buf[4096]; + +int fz_skip(fz_context *ctx, fz_stream *stm, int len) +{ + int count, l, total = 0; + + while (len) + { + l = len; + if (l > sizeof(skip_buf)) + l = sizeof(skip_buf); + count = fz_read(ctx, stm, skip_buf, l); + total += count; + if (count < l) + break; + len -= count; + } + return total; +} + fz_buffer * fz_read_all(fz_context *ctx, fz_stream *stm, int initial) { diff --git a/source/fitz/svg-device.c b/source/fitz/svg-device.c index d36a5872..5f60cf75 100644 --- a/source/fitz/svg-device.c +++ b/source/fitz/svg-device.c @@ -743,7 +743,7 @@ svg_dev_fill_image(fz_context *ctx, fz_device *dev, fz_image *image, const fz_ma break; default: { - fz_buffer *buf = fz_new_buffer_from_image_as_png(ctx, image, image->w, image->h); + fz_buffer *buf = fz_new_buffer_from_image_as_png(ctx, image); fz_printf(ctx, out, "image/png;base64,"); send_data_base64(ctx, out, buf); fz_drop_buffer(ctx, buf); @@ -830,7 +830,7 @@ svg_dev_fill_image_mask(fz_context *ctx, fz_device *dev, fz_image *image, const break; default: { - fz_buffer *buf = fz_new_buffer_from_image_as_png(ctx, image, image->w, image->h); + fz_buffer *buf = fz_new_buffer_from_image_as_png(ctx, image); fz_printf(ctx, out, "image/png;base64,"); send_data_base64(ctx, out, buf); fz_drop_buffer(ctx, buf); @@ -874,7 +874,7 @@ svg_dev_clip_image_mask(fz_context *ctx, fz_device *dev, fz_image *image, const break; default: { - fz_buffer *buf = fz_new_buffer_from_image_as_png(ctx, image, image->w, image->h); + fz_buffer *buf = fz_new_buffer_from_image_as_png(ctx, image); fz_printf(ctx, out, "image/png;base64,"); send_data_base64(ctx, out, buf); fz_drop_buffer(ctx, buf); diff --git a/source/fitz/test-device.c b/source/fitz/test-device.c index 64c7804f..6a4e0127 100644 --- a/source/fitz/test-device.c +++ b/source/fitz/test-device.c @@ -184,7 +184,7 @@ fz_test_fill_image(fz_context *ctx, fz_device *dev, fz_image *image, const fz_ma return; } - pix = fz_get_pixmap_from_image(ctx, image, 0, 0); + pix = fz_get_pixmap_from_image(ctx, image, NULL, NULL, 0, 0); if (pix == NULL) /* Should never happen really, but... */ return; |