[Spice-devel] [vdagent-win PATCH v4 3/5] Write code to decode PNG format

Christophe de Dinechin cdupontd at redhat.com
Wed Jul 19 08:07:04 UTC 2017


> On 18 Jul 2017, at 17:54, Christophe Fergeau <cfergeau at redhat.com> wrote:
> 
> On Mon, Jul 17, 2017 at 09:54:04AM +0100, Frediano Ziglio wrote:
>> Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
>> ---
>> Makefile.am          |   6 +-
>> configure.ac         |   3 +
>> vdagent/image.cpp    |   8 +-
>> vdagent/imagepng.cpp | 244 +++++++++++++++++++++++++++++++++++++++++++++++++++
>> vdagent/imagepng.h   |  25 ++++++
>> 5 files changed, 277 insertions(+), 9 deletions(-)
>> create mode 100644 vdagent/imagepng.cpp
>> create mode 100644 vdagent/imagepng.h
>> 
>> diff --git a/Makefile.am b/Makefile.am
>> index b35dd57..175d8f7 100644
>> --- a/Makefile.am
>> +++ b/Makefile.am
>> @@ -23,8 +23,8 @@ LIBS = -lversion
>> 
>> bin_PROGRAMS = vdagent vdservice
>> 
>> -vdagent_LDADD = -lwtsapi32 -lgdi32 vdagent_rc.$(OBJEXT)
>> -vdagent_CXXFLAGS = $(AM_CXXFLAGS)
>> +vdagent_LDADD = $(LIBPNG_LIBS) $(ZLIB_LIBS) -lwtsapi32 -lgdi32 vdagent_rc.$(OBJEXT)
>> +vdagent_CXXFLAGS = $(AM_CXXFLAGS) $(LIBPNG_CFLAGS)
>> vdagent_LDFLAGS = $(AM_LDFLAGS) -Wl,--subsystem,windows
>> vdagent_SOURCES =			\
>> 	common/vdcommon.cpp             \
>> @@ -44,6 +44,8 @@ vdagent_SOURCES =			\
>> 	vdagent/as_user.h		\
>> 	vdagent/image.cpp		\
>> 	vdagent/image.h			\
>> +	vdagent/imagepng.cpp		\
>> +	vdagent/imagepng.h		\
>> 	$(NULL)
>> 
>> vdagent_rc.$(OBJEXT): vdagent/vdagent.rc
>> diff --git a/configure.ac b/configure.ac
>> index 4eac4b4..bb33075 100644
>> --- a/configure.ac
>> +++ b/configure.ac
>> @@ -101,6 +101,9 @@ dnl ---------------------------------------------------------------------------
>> dnl - Check library dependencies
>> dnl ---------------------------------------------------------------------------
>> 
>> +PKG_CHECK_MODULES(LIBPNG, [libpng])
>> +PKG_CHECK_MODULES(ZLIB, [zlib])
>> +
>> dnl ---------------------------------------------------------------------------
>> dnl - Makefiles, etc.
>> dnl ---------------------------------------------------------------------------
>> diff --git a/vdagent/image.cpp b/vdagent/image.cpp
>> index b32da6f..c968217 100644
>> --- a/vdagent/image.cpp
>> +++ b/vdagent/image.cpp
>> @@ -21,9 +21,9 @@
>> 
>> #include "vdcommon.h"
>> #include "image.h"
>> +#include "imagepng.h"
>> 
>> ImageCoder *create_bitmap_coder();
>> -ImageCoder *create_png_coder();
>> 
>> static ImageCoder *get_coder(uint32_t vdagent_type)
>> {
>> @@ -172,9 +172,3 @@ ImageCoder *create_bitmap_coder()
>> {
>>     return new BitmapCoder();
>> }
>> -
>> -// TODO
>> -ImageCoder *create_png_coder()
>> -{
>> -    return NULL;
>> -}
>> diff --git a/vdagent/imagepng.cpp b/vdagent/imagepng.cpp
>> new file mode 100644
>> index 0000000..19d9efc
>> --- /dev/null
>> +++ b/vdagent/imagepng.cpp
>> @@ -0,0 +1,244 @@
>> +/*
>> +   Copyright (C) 2017 Red Hat, Inc.
>> +
>> +   This program is free software; you can redistribute it and/or
>> +   modify it under the terms of the GNU General Public License as
>> +   published by the Free Software Foundation; either version 2 of
>> +   the License, or (at your option) any later version.
>> +
>> +   This program is distributed in the hope that it will be useful,
>> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
>> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>> +   GNU General Public License for more details.
>> +
>> +   You should have received a copy of the GNU General Public License
>> +   along with this program.  If not, see <http://www.gnu.org/licenses/>.
>> +*/
>> +
>> +#include "vdcommon.h"
>> +
>> +#include <png.h>
>> +#include <algorithm>
>> +
>> +#include "imagepng.h"
>> +
>> +class PngCoder: public ImageCoder
>> +{
>> +public:
>> +    PngCoder() {};
>> +    size_t get_dib_size(const uint8_t *data, size_t size);
>> +    void get_dib_data(uint8_t *dib, const uint8_t *data, size_t size);
>> +    uint8_t *from_bitmap(const BITMAPINFO& info, const void *bits, long &size);
>> +private:
>> +    size_t convert_to_dib(uint8_t *out_buf, const uint8_t *data, size_t size);
>> +};
>> +
>> +struct BufferIo {
>> +    uint8_t *buf;
>> +    uint32_t pos, size;
>> +};
>> +
>> +static void read_from_bufio(png_structp png, png_bytep out, png_size_t size)
>> +{
>> +    BufferIo& io(*(BufferIo*)png_get_io_ptr(png));

As long as you do a cast form a pointer, I would rather use a pointer to BufferIo here.

>> +    if (io.pos + size >= io.size)
>> +        png_error(png, "read past end");
>> +    memcpy(out, io.buf+io.pos, size);

This memcpy should be in ‘else’, unless png_error interrupts the flow of control (I saw a png_jumpbuf below that frightens me).

>> +    io.pos += size;
>> +}
>> +
>> +size_t PngCoder::get_dib_size(const uint8_t *data, size_t size)
>> +{
>> +    return convert_to_dib(NULL, data, size);
>> +}
>> +
>> +typedef void line_fixup_t(uint8_t *line, unsigned width);
>> +
>> +static void line_fixup_none(uint8_t *line, unsigned width)
>> +{
>> +}
>> +
>> +static void line_fixup_2to4(uint8_t *line, unsigned width)
> 
> Might be slightly more readable with 'bpp' in the name,
> line_fixup_2bpp_to_4bpp()
> 
>> +{
>> +    width = (width + 3) / 4u;
>> +    while (width--) {
>> +        uint8_t from = line[width];
>> +        line[width*2+1] = ((from & 0x03) << 0) | ((from & 0x0c) << 2);
>> +        line[width*2+0] = ((from & 0x30) >> 4) | ((from & 0xc0) >> 2);
>> +    }
>> +}
>> +
>> +static void line_fixup_rgb2bgr(uint8_t *line, unsigned width)
> 
> 'unsigned int' (applies to several parts of the file).

I’m curious why you think this is an improvement? ‘unsigned’ always means ‘unsigned int’, and two-words types always feel weird.

> 
>> +{
>> +    for (; width; --width) {
>> +        std::swap(line[0], line[2]);
>> +        line += 3;
>> +    }
> 
> Not really a fan of the over short for() loop, could just be
> for (unsigned int i = 0; i < width; i++),

If not initializing, it’s just while(width—).

> or something closer to what
> you do in line_fixup_2to4 (which I'm don't like much either ;)).
> And I don't think it's really useful to use std::swap to swap two uint8_t.

std::swap is there. Why not use it? In this day and age, manually swapping stuff in C++ is considered quite inelegant, even for standard types.

> 
> 
> 
>> +}
>> +
>> +size_t PngCoder::convert_to_dib(uint8_t *out_buf, const uint8_t *data, size_t size)
>> +{
>> +    BufferIo io;
>> +    io.buf = (uint8_t *)data;
>> +    io.pos = 0;
>> +    io.size = size;

Since you are using C++, consider adding a constructor to BufferIo.

>> +
>> +    png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
>> +    if (!png)
>> +        return 0;
>> +
>> +    png_infop info = png_create_info_struct(png);
>> +    if (!info) {
>> +        png_destroy_read_struct(&png, &info, NULL);
>> +        return 0;
>> +    }
>> +
>> +    if (setjmp(png_jmpbuf(png))) {
>> +        png_destroy_read_struct(&png, &info, NULL);
>> +        return 0;
>> +    }
>> +
>> +    png_set_read_fn(png, &io, read_from_bufio);
>> +
>> +    png_read_info(png, info);
>> +
>> +    // not so much precision is supported
>> +    unsigned bits = png_get_bit_depth(png, info);
>> +    if (bits == 16)
>> +        png_set_strip_16(png);
>> +
>> +    unsigned out_bits;
>> +    bool is_gray = false;
>> +    line_fixup_t *line_fixup = line_fixup_none;
>> +    switch (png_get_color_type(png, info)) {
>> +    case PNG_COLOR_TYPE_GRAY:
>> +        is_gray = true;
>> +        out_bits = bits == 16 ? 8 : bits;
>> +        if (bits == 2) {
> 
> Can you rework these 2 lines? I would not use a ternary '?' here when
> you need to test on multiple different values of 'bits' (2 or 16).
> Regular if, or a switch/case should do the trick and be more readable.
> 
>> +#if 0
>> +            png_set_expand_gray_1_2_4_to_8(png);
>> +            out_bits = 8;
>> +#else
>> +            line_fixup = line_fixup_2to4;
>> +            out_bits = 4;
>> +#endif
> 
> Is the #if 0 intentional?
> 
> 
>> +        }
>> +        break;
>> +    case PNG_COLOR_TYPE_PALETTE:
>> +        // should return 1, 2, 4 and 8, BMP does not support 2
> 
> Is the first '2' unwanted? Since you say later 2 is unsupported
> 
>> +        out_bits = bits;
>> +        if (bits == 2) {
>> +            line_fixup = line_fixup_2to4;
>> +            out_bits = 4;
>> +        }
>> +        break;
>> +    case PNG_COLOR_TYPE_RGB:
>> +        line_fixup = line_fixup_rgb2bgr;
>> +        out_bits = 24;
>> +        break;
>> +    case PNG_COLOR_TYPE_RGB_ALPHA:
>> +        line_fixup = line_fixup_rgb2bgr;
>> +        out_bits = 24;
>> +        png_set_strip_alpha(png);
>> +        break;
>> +    case PNG_COLOR_TYPE_GRAY_ALPHA:
>> +        is_gray = true;
>> +        // gray with alpha should always be 8 bit but make it sure
>> +        // in case format change
>> +        png_set_expand_gray_1_2_4_to_8(png);
>> +        out_bits = 8;
>> +        png_set_strip_alpha(png);
>> +        break;
>> +    default:
>> +        longjmp(png_jmpbuf(png), 1);

Ouch.

Some png_error maybe?

>> +        break;
>> +    }
>> +
>> +    const unsigned width = png_get_image_width(png, info);
>> +    const unsigned height = png_get_image_height(png, info);
>> +    const size_t stride = compute_dib_stride(width, out_bits);
>> +    const size_t image_size = stride * height;
>> +    const size_t palette_size = out_bits > 8 ? 0 : sizeof(RGBQUAD) * (1 << out_bits);
>> +    const size_t dib_size = sizeof(BITMAPINFOHEADER) + palette_size + image_size;
>> +    if (!out_buf) {
>> +        png_destroy_read_struct(&png, &info, NULL);
>> +        return dib_size;
>> +    }
>> +
>> +    // TODO tests
>> +    // bits, 1, 2, 4, 8, 16
>> +    // all color types
>> +    // alpha/not alpha
>> +    // indexed with not all colors
>> +
>> +    // fill header
>> +    BITMAPINFOHEADER& head(*(BITMAPINFOHEADER *)out_buf);
>> +    memset(&head, 0, sizeof(head));
>> +    head.biSize = sizeof(head);

And that’s the fourth coding style in this file alone, camelcase hungarian ;-)


>> +    head.biWidth = width;
>> +    head.biHeight = height;
>> +    head.biPlanes = 1;
>> +    head.biBitCount = out_bits;
>> +    head.biCompression = BI_RGB;
>> +    head.biSizeImage = image_size;
>> +
>> +    // copy palette
>> +    RGBQUAD *rgb = (RGBQUAD *)(out_buf + sizeof(BITMAPINFOHEADER));
>> +    if (is_gray) {
>> +        const unsigned colors = 1 << out_bits;
>> +        const unsigned mult = 255 / (colors - 1);
>> +        for (unsigned color = 0; color < colors; ++color) {
>> +            rgb->rgbBlue = rgb->rgbGreen = rgb->rgbRed = color * mult;
>> +            rgb->rgbReserved = 0;
>> +            ++rgb;
>> +        }
>> +        head.biClrUsed = colors;
>> +    } else if (out_bits <= 8) {
>> +        png_colorp palette = NULL;
>> +        int num_palette;
>> +        if (!png_get_PLTE(png, info, &palette, &num_palette)) {
>> +            png_error(png, "error getting palette");
>> +        }
>> +        const int colors = 1 << out_bits;
>> +        for (int color = 0; color < colors; ++color) {
>> +            if (color < num_palette) {
>> +                rgb->rgbBlue = palette->blue;
>> +                rgb->rgbGreen = palette->green;
>> +                rgb->rgbRed = palette->red;
>> +            } else {
>> +                rgb->rgbBlue = rgb->rgbGreen = rgb->rgbRed = 0;
>> +            }
>> +            rgb->rgbReserved = 0;
>> +            ++rgb;
>> +            ++palette;
>> +        }
>> +        head.biClrUsed = colors;
>> +    }
>> +
>> +    // now do the actual conversion!
>> +    uint8_t *dst = out_buf + sizeof(BITMAPINFOHEADER) + palette_size + image_size;
>> +    for (unsigned row = 0; row < height; ++row) {
>> +        ((uint32_t*)dst)[-1] = 0; // padding
>> +        dst -= stride;
>> +        png_read_row(png, dst, NULL);
>> +        line_fixup(dst, width);
>> +    }
>> +
>> +    png_destroy_read_struct(&png, &info, NULL);
>> +    return dib_size;
>> +}
>> +
>> +void PngCoder::get_dib_data(uint8_t *dib, const uint8_t *data, size_t size)
>> +{
>> +    convert_to_dib(dib, data, size);
>> +}
>> +
>> +uint8_t *PngCoder::from_bitmap(const BITMAPINFO& info, const void *bits, long &size)
>> +{
>> +    return NULL;
>> +}
> 
> Fwiw, I would have added a comment as to why it's returning NULL
> (encoding to be implemented)
> 
> Christophe
> _______________________________________________
> Spice-devel mailing list
> Spice-devel at lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/spice-devel



More information about the Spice-devel mailing list