[PATCH 4/4] gdi32: Partially implement HALFTONE stretch mode.

Huw Davies huw at codeweavers.com
Mon May 24 03:21:48 CDT 2021


On Mon, May 17, 2021 at 05:00:29PM +0800, Zhiyi Zhang wrote:
> COLORONCOLOR(STRETCH_DELETESCANS) was used in place of HALFTONE. COLORONCOLOR mode may delete rows
> of pixels without trying to preserve information so it will cause Wine to render poorly when the
> destination rectangle is small.
> 
> According to tests, HALFTONE mode uses box filter when doing integer downscaling and nearest
> neighbor interpolation when doing upscaling in both horizontally and vertically. In other cases,
> HALFTONE mode uses a lanczos3 like algorithm to interpolate pixels. There are also other heuristics
> involved. For example, shrinking a 2x2 image to 1x1 may not use box filter depending on image data.
> Since this algorithm is undocumented, it's difficult to reverse engineer the original algorithm and
> produce identical results. Instead, this patch uses a naive implementation of bilinear interpolation
> to implement HALFTONE mode and it produces good quality images.
> 
> For 8-bit and lower color depth images, nulldrv_StretchBlt should resize the images first and then
> converts color depth.
> 
> Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=46375
> Signed-off-by: Zhiyi Zhang <zzhang at codeweavers.com>
> ---
>  dlls/gdi32/dibdrv/bitblt.c     |   89 +++
>  dlls/gdi32/dibdrv/dibdrv.h     |    1 +
>  dlls/gdi32/dibdrv/primitives.c |   81 +++
>  dlls/gdi32/tests/bitmap.c      | 1136 ++++++++++++++++++++++++++++++++
>  4 files changed, 1307 insertions(+)
> 
> diff --git a/dlls/gdi32/dibdrv/bitblt.c b/dlls/gdi32/dibdrv/bitblt.c
> index d2f59965b02..ff12a7ca1dd 100644
> --- a/dlls/gdi32/dibdrv/bitblt.c
> +++ b/dlls/gdi32/dibdrv/bitblt.c
> @@ -1193,6 +1193,88 @@ static DWORD calc_1d_stretch_params( INT dst_start, INT dst_length, INT dst_vis_
>      return ERROR_SUCCESS;
>  }
>  
> +static void halftone( const dib_info *dst_dib, struct bitblt_coords *dst,
> +                      const dib_info *src_dib, struct bitblt_coords *src )
> +{
> +    int src_start_x, src_start_y, dst_x, dst_y, x0, x1, y0, y1;
> +    int left_most, right_most, top_most, bottom_most;
> +    int src_width, src_height, dst_width, dst_height;
> +    float float_x, float_y, dx, dy, inc_x, inc_y;
> +    BOOL mirrored_x, mirrored_y;
> +    COLORREF c00, c01, c10, c11;
> +    RECT src_rect, dst_rect;
> +    BYTE r, g, b;
> +
> +    get_bounding_rect( &src_rect, src->x, src->y, src->width, src->height );
> +    get_bounding_rect( &dst_rect, dst->x, dst->y, dst->width, dst->height );
> +    intersect_rect( &src_rect, &src->visrect, &src_rect );
> +    intersect_rect( &dst_rect, &dst->visrect, &dst_rect );
> +    offset_rect( &dst_rect, -dst_rect.left, -dst_rect.top );
> +
> +    left_most = src_rect.left;
> +    right_most = src_rect.right - 1;
> +    top_most = src_rect.top;
> +    bottom_most = src_rect.bottom - 1;
> +
> +    src_width = src_rect.right - src_rect.left;
> +    src_height = src_rect.bottom - src_rect.top;
> +    dst_width = dst_rect.right - dst_rect.left;
> +    dst_height = dst_rect.bottom - dst_rect.top;
> +
> +    mirrored_x = (dst->width < 0) != (src->width < 0);
> +    mirrored_y = (dst->height < 0) != (src->height < 0);
> +    src_start_x = mirrored_x ? right_most : left_most;
> +    src_start_y = mirrored_y ? bottom_most : top_most;
> +    inc_x = mirrored_x ? -(float)src_width / dst_width : (float)src_width / dst_width;
> +    inc_y = mirrored_y ? -(float)src_height / dst_height : (float)src_height / dst_height;
> +
> +    for (dst_y = dst_rect.top; dst_y < dst_rect.bottom; ++dst_y)
> +    {
> +        for (dst_x = dst_rect.left; dst_x < dst_rect.right; ++dst_x)
> +        {
> +            float_x = src_start_x + (dst_x - dst_rect.left) * inc_x;
> +            float_y = src_start_y + (dst_y - dst_rect.top) * inc_y;
> +            float_x = clampf(float_x, left_most, right_most);
> +            float_y = clampf(float_y, top_most, bottom_most);
> +            x0 = float_x;
> +            y0 = float_y;
> +            x1 = clamp(x0 + 1, left_most, right_most);
> +            y1 = clamp(y0 + 1, top_most, bottom_most);
> +            dx = float_x - x0;
> +            dy = float_y - y0;
> +
> +            c00 = src_dib->funcs->pixel_to_colorref(src_dib, src_dib->funcs->get_pixel(src_dib, x0, y0));
> +            c01 = src_dib->funcs->pixel_to_colorref(src_dib, src_dib->funcs->get_pixel(src_dib, x1, y0));
> +            c10 = src_dib->funcs->pixel_to_colorref(src_dib, src_dib->funcs->get_pixel(src_dib, x0, y1));
> +            c11 = src_dib->funcs->pixel_to_colorref(src_dib, src_dib->funcs->get_pixel(src_dib, x1, y1));
> +            r = bilinear_interpolate(GetRValue(c00), GetRValue(c01), GetRValue(c10), GetRValue(c11), dx, dy);
> +            g = bilinear_interpolate(GetGValue(c00), GetGValue(c01), GetGValue(c10), GetGValue(c11), dx, dy);
> +            b = bilinear_interpolate(GetBValue(c00), GetBValue(c01), GetBValue(c10), GetBValue(c11), dx, dy);
> +            dst_dib->funcs->set_colorref(dst_dib, dst_x, dst_y, r, g, b);
> +        }
> +    }

You should be able to move these loops to the primitives which would
avoid doing explicit get_pixel() / set_"pixel"() calls.

> index fe3482671b2..c28dde8159b 100644
> --- a/dlls/gdi32/tests/bitmap.c
> +++ b/dlls/gdi32/tests/bitmap.c
> @@ -57,6 +57,29 @@ static inline int get_dib_image_size( const BITMAPINFO *info )
>          * abs( info->bmiHeader.biHeight );
>  }
>  
> @@ -3426,6 +3449,1118 @@ static void test_StretchBlt(void)
>      DeleteDC(hdcScreen);
>  }
>  
> +static void test_StretchBlt_HALFTONE(void)
> +{
> +#define PAD_ZERO 0x00,
> +
> +    /* Same dimension */
> +    /* 1x1 -> 1x1 */
> +    static const unsigned char test_bits_0[] =
> +    {
> +        0xff, 0x7f, 0x00,
> +    };
> +    static const unsigned char expected_bits_0[] =
> +    {
> +        0xff, 0x7f, 0x00,
> +    };
> +

I think these tests would be better inside test/dib.c using the
framework we already have for dib tests (I know there are some
StretchBlt tests here, but those are historical).  If possible you'd
want to pick a few examples of sizes that you can correctly implement.
We can always add more corner cases if we need to tweak the algorithm
later on.

Huw.



More information about the wine-devel mailing list