]> git.ipfire.org Git - thirdparty/plymouth.git/commitdiff
pixel-buffer: add the concept of a device scale
authorGiovanni Campagna <gcampagna@src.gnome.org>
Sun, 18 Jan 2015 09:56:33 +0000 (01:56 -0800)
committerRay Strode <rstrode@redhat.com>
Thu, 3 Mar 2016 19:32:24 +0000 (14:32 -0500)
The device scale is a scale transformation that is applied to
the pixel buffer contents, but is transparent to the user of
the buffer (all pixel values are in logical pixels, not device
pixels).
The concept is modeled after the cairo API, and it is useful
to implement HiDPI monitor support.

https://bugs.freedesktop.org/show_bug.cgi?id=84482

src/libply-splash-core/ply-pixel-buffer.c
src/libply-splash-core/ply-pixel-buffer.h

index 61ee91c485c24884ad6cce60e7979800ecc4e6a0..52a3f86e255243e47b1b5821ca46562a9a674701 100644 (file)
@@ -43,11 +43,13 @@ struct _ply_pixel_buffer
 {
         uint32_t       *bytes;
 
-        ply_rectangle_t area;
-        ply_list_t     *clip_areas;
+        ply_rectangle_t area; /* in device pixels */
+        ply_rectangle_t logical_area; /* in logical pixels */
+        ply_list_t     *clip_areas; /* in device pixels */
 
-        ply_region_t   *updated_areas;
+        ply_region_t   *updated_areas; /* in device pixels */
         uint32_t        is_opaque : 1;
+        int             device_scale;
 };
 
 static inline void ply_pixel_buffer_blend_value_at_pixel (ply_pixel_buffer_t *buffer,
@@ -168,6 +170,34 @@ ply_pixel_buffer_blend_value_at_pixel (ply_pixel_buffer_t *buffer,
         buffer->bytes[y * buffer->area.width + x] = pixel_value;
 }
 
+static void
+ply_rectangle_upscale (ply_rectangle_t *area,
+                       int              scale)
+{
+        area->x *= scale;
+        area->y *= scale;
+        area->width *= scale;
+        area->height *= scale;
+}
+
+static void
+ply_rectangle_downscale (ply_rectangle_t *area,
+                         int              scale)
+{
+        area->x /= scale;
+        area->y /= scale;
+        area->width /= scale;
+        area->height /= scale;
+}
+
+static void
+ply_pixel_buffer_adjust_area_for_device_scale (ply_pixel_buffer_t *buffer,
+                                               ply_rectangle_t    *area)
+{
+        ply_rectangle_upscale (area, buffer->device_scale);
+}
+
+/* this function will also convert logical pixels to device pixels */
 static void
 ply_pixel_buffer_crop_area_to_clip_area (ply_pixel_buffer_t *buffer,
                                          ply_rectangle_t    *area,
@@ -176,6 +206,7 @@ ply_pixel_buffer_crop_area_to_clip_area (ply_pixel_buffer_t *buffer,
         ply_list_node_t *node;
 
         *cropped_area = *area;
+        ply_pixel_buffer_adjust_area_for_device_scale (buffer, cropped_area);
 
         node = ply_list_get_first_node (buffer->clip_areas);
         while (node != NULL) {
@@ -199,6 +230,9 @@ ply_pixel_buffer_fill_area_with_pixel_value (ply_pixel_buffer_t *buffer,
         unsigned long row, column;
         ply_rectangle_t cropped_area;
 
+        if (fill_area == NULL)
+                fill_area = &buffer->logical_area;
+
         ply_pixel_buffer_crop_area_to_clip_area (buffer, fill_area, &cropped_area);
 
         /* If we're filling the entire buffer with a fully opaque color,
@@ -216,6 +250,8 @@ ply_pixel_buffer_fill_area_with_pixel_value (ply_pixel_buffer_t *buffer,
                                                                pixel_value);
                 }
         }
+
+        ply_region_add_rectangle (buffer->updated_areas, &cropped_area);
 }
 
 void
@@ -227,6 +263,8 @@ ply_pixel_buffer_push_clip_area (ply_pixel_buffer_t *buffer,
         new_clip_area = malloc (sizeof(*new_clip_area));
 
         *new_clip_area = *clip_area;
+        ply_pixel_buffer_adjust_area_for_device_scale (buffer, new_clip_area);
+
         ply_list_append_data (buffer->clip_areas, new_clip_area);
 }
 
@@ -252,6 +290,8 @@ ply_pixel_buffer_new (unsigned long width,
         buffer->bytes = (uint32_t *) calloc (height, width * sizeof(uint32_t));
         buffer->area.width = width;
         buffer->area.height = height;
+        buffer->logical_area = buffer->area;
+        buffer->device_scale = 1;
 
         buffer->clip_areas = ply_list_new ();
         ply_pixel_buffer_push_clip_area (buffer, &buffer->area);
@@ -290,21 +330,21 @@ ply_pixel_buffer_get_size (ply_pixel_buffer_t *buffer,
         assert (buffer != NULL);
         assert (size != NULL);
 
-        *size = buffer->area;
+        *size = buffer->logical_area;
 }
 
 unsigned long
 ply_pixel_buffer_get_width (ply_pixel_buffer_t *buffer)
 {
         assert (buffer != NULL);
-        return buffer->area.width;
+        return buffer->logical_area.width;
 }
 
 unsigned long
 ply_pixel_buffer_get_height (ply_pixel_buffer_t *buffer)
 {
         assert (buffer != NULL);
-        return buffer->area.height;
+        return buffer->logical_area.height;
 }
 
 bool
@@ -386,7 +426,7 @@ ply_pixel_buffer_fill_with_gradient (ply_pixel_buffer_t *buffer,
         ply_rectangle_t cropped_area;
 
         if (fill_area == NULL)
-                fill_area = &buffer->area;
+                fill_area = &buffer->logical_area;
 
         ply_pixel_buffer_crop_area_to_clip_area (buffer, fill_area, &cropped_area);
 
@@ -457,24 +497,16 @@ ply_pixel_buffer_fill_with_color (ply_pixel_buffer_t *buffer,
                                   double              alpha)
 {
         uint32_t pixel_value;
-        ply_rectangle_t cropped_area;
 
         assert (buffer != NULL);
 
-        if (fill_area == NULL)
-                fill_area = &buffer->area;
-
-        ply_pixel_buffer_crop_area_to_clip_area (buffer, fill_area, &cropped_area);
-
         red *= alpha;
         green *= alpha;
         blue *= alpha;
 
         pixel_value = PLY_PIXEL_BUFFER_COLOR_TO_PIXEL_VALUE (red, green, blue, alpha);
 
-        ply_pixel_buffer_fill_area_with_pixel_value (buffer, &cropped_area, pixel_value);
-
-        ply_region_add_rectangle (buffer->updated_areas, &cropped_area);
+        ply_pixel_buffer_fill_area_with_pixel_value (buffer, fill_area, pixel_value);
 }
 
 void
@@ -483,7 +515,6 @@ ply_pixel_buffer_fill_with_hex_color_at_opacity (ply_pixel_buffer_t *buffer,
                                                  uint32_t            hex_color,
                                                  double              opacity)
 {
-        ply_rectangle_t cropped_area;
         uint32_t pixel_value;
         double red;
         double green;
@@ -492,11 +523,6 @@ ply_pixel_buffer_fill_with_hex_color_at_opacity (ply_pixel_buffer_t *buffer,
 
         assert (buffer != NULL);
 
-        if (fill_area == NULL)
-                fill_area = &buffer->area;
-
-        ply_pixel_buffer_crop_area_to_clip_area (buffer, fill_area, &cropped_area);
-
         /* if they only gave an rgb hex number, assume an alpha of 0xff
          */
         if ((hex_color & 0xff000000) == 0)
@@ -515,9 +541,7 @@ ply_pixel_buffer_fill_with_hex_color_at_opacity (ply_pixel_buffer_t *buffer,
 
         pixel_value = PLY_PIXEL_BUFFER_COLOR_TO_PIXEL_VALUE (red, green, blue, alpha);
 
-        ply_pixel_buffer_fill_area_with_pixel_value (buffer, &cropped_area, pixel_value);
-
-        ply_region_add_rectangle (buffer->updated_areas, &cropped_area);
+        ply_pixel_buffer_fill_area_with_pixel_value (buffer, fill_area, pixel_value);
 }
 
 void
@@ -529,48 +553,120 @@ ply_pixel_buffer_fill_with_hex_color (ply_pixel_buffer_t *buffer,
                                                                 hex_color, 1.0);
 }
 
+static inline uint32_t
+ply_pixels_interpolate (uint32_t *bytes,
+                        int       width,
+                        int       height,
+                        double    x,
+                        double    y)
+{
+        int ix;
+        int iy;
+        int i;
+        int offset_x;
+        int offset_y;
+        uint32_t pixels[2][2];
+        uint32_t reply = 0;
+
+        for (offset_y = 0; offset_y < 2; offset_y++) {
+                for (offset_x = 0; offset_x < 2; offset_x++) {
+                        ix = x + offset_x;
+                        iy = y + offset_y;
+
+                        if (ix < 0 || ix >= width || iy < 0 || iy >= height)
+                                pixels[offset_y][offset_x] = 0x00000000;
+                        else
+                                pixels[offset_y][offset_x] = bytes[ix + iy * width];
+                }
+        }
+        if (!pixels[0][0] && !pixels[0][1] && !pixels[1][0] && !pixels[1][1]) return 0;
+
+        ix = x;
+        iy = y;
+        x -= ix;
+        y -= iy;
+        for (i = 0; i < 4; i++) {
+                uint32_t value = 0;
+                uint32_t mask = 0xFF << (i * 8);
+                value += ((pixels[0][0]) & mask) * (1 - x) * (1 - y);
+                value += ((pixels[0][1]) & mask) * x * (1 - y);
+                value += ((pixels[1][0]) & mask) * (1 - x) * y;
+                value += ((pixels[1][1]) & mask) * x * y;
+                reply |= value & mask;
+        }
+        return reply;
+}
+
 void
-ply_pixel_buffer_fill_with_argb32_data_at_opacity_with_clip (ply_pixel_buffer_t *buffer,
-                                                             ply_rectangle_t    *fill_area,
-                                                             ply_rectangle_t    *clip_area,
-                                                             uint32_t           *data,
-                                                             double              opacity)
+ply_pixel_buffer_fill_with_argb32_data_at_opacity_with_clip_and_scale (ply_pixel_buffer_t *buffer,
+                                                                       ply_rectangle_t    *fill_area,
+                                                                       ply_rectangle_t    *clip_area,
+                                                                       uint32_t           *data,
+                                                                       double              opacity,
+                                                                       int                 scale)
 {
         unsigned long row, column;
         uint8_t opacity_as_byte;
+        ply_rectangle_t logical_fill_area;
         ply_rectangle_t cropped_area;
         unsigned long x;
         unsigned long y;
+        double scale_factor;
 
         assert (buffer != NULL);
 
-        if (fill_area == NULL)
-                fill_area = &buffer->area;
+        if (fill_area == NULL) {
+                fill_area = &buffer->logical_area;
+                logical_fill_area = buffer->logical_area;
+        } else {
+                logical_fill_area = *fill_area;
+                ply_rectangle_downscale (&logical_fill_area, scale);
+        }
 
-        ply_pixel_buffer_crop_area_to_clip_area (buffer, fill_area, &cropped_area);
+        ply_pixel_buffer_crop_area_to_clip_area (buffer, &logical_fill_area, &cropped_area);
+
+        if (clip_area) {
+                ply_rectangle_t device_clip_area;
 
-        if (clip_area)
-                ply_rectangle_intersect (&cropped_area, clip_area, &cropped_area);
+                device_clip_area = *clip_area;
+                ply_rectangle_downscale (&device_clip_area, scale);
+                ply_pixel_buffer_adjust_area_for_device_scale (buffer, &device_clip_area);
+                ply_rectangle_intersect (&cropped_area, &device_clip_area, &cropped_area);
+        }
 
         if (cropped_area.width == 0 || cropped_area.height == 0)
                 return;
 
-        x = cropped_area.x - fill_area->x;
-        y = cropped_area.y - fill_area->y;
         opacity_as_byte = (uint8_t) (opacity * 255.0);
+        scale_factor = (double)scale / buffer->device_scale;
+        x = cropped_area.x;
+        y = cropped_area.y;
 
+        /* column, row are the point we want to write into, in
+           pixel_buffer coordinate space (device pixels)
+
+           scale_factor * (column - fill_area->x), scale_factor * (row - fill_area->y)
+           is the point we want to source from, in the data coordinate
+           space */
         for (row = y; row < y + cropped_area.height; row++) {
                 for (column = x; column < x + cropped_area.width; column++) {
                         uint32_t pixel_value;
 
-                        pixel_value = data[fill_area->width * row + column];
+                        if (buffer->device_scale == scale)
+                                pixel_value = data[fill_area->width * (row - fill_area->y) +
+                                                   column - fill_area->x];
+                        else
+                                pixel_value = ply_pixels_interpolate (data,
+                                                                      fill_area->width,
+                                                                      fill_area->height,
+                                                                      scale_factor * column - fill_area->x,
+                                                                      scale_factor * row - fill_area->y);
                         if ((pixel_value >> 24) == 0x00)
                                 continue;
 
                         pixel_value = make_pixel_value_translucent (pixel_value, opacity_as_byte);
                         ply_pixel_buffer_blend_value_at_pixel (buffer,
-                                                               cropped_area.x + (column - x),
-                                                               cropped_area.y + (row - y),
+                                                               column, row,
                                                                pixel_value);
                 }
         }
@@ -578,16 +674,31 @@ ply_pixel_buffer_fill_with_argb32_data_at_opacity_with_clip (ply_pixel_buffer_t
         ply_region_add_rectangle (buffer->updated_areas, &cropped_area);
 }
 
+void
+ply_pixel_buffer_fill_with_argb32_data_at_opacity_with_clip (ply_pixel_buffer_t *buffer,
+                                                             ply_rectangle_t    *fill_area,
+                                                             ply_rectangle_t    *clip_area,
+                                                             uint32_t           *data,
+                                                             double              opacity)
+{
+        ply_pixel_buffer_fill_with_argb32_data_at_opacity_with_clip_and_scale (buffer,
+                                                                               fill_area,
+                                                                               clip_area,
+                                                                               data,
+                                                                               opacity,
+                                                                               1);
+}
+
 void
 ply_pixel_buffer_fill_with_argb32_data_at_opacity (ply_pixel_buffer_t *buffer,
                                                    ply_rectangle_t    *fill_area,
                                                    uint32_t           *data,
                                                    double              opacity)
 {
-        ply_pixel_buffer_fill_with_argb32_data_at_opacity_with_clip (buffer,
-                                                                     fill_area,
-                                                                     NULL,
-                                                                     data, opacity);
+        ply_pixel_buffer_fill_with_argb32_data_at_opacity_with_clip_and_scale (buffer,
+                                                                               fill_area,
+                                                                               NULL,
+                                                                               data, opacity, 1);
 }
 
 void
@@ -595,10 +706,10 @@ ply_pixel_buffer_fill_with_argb32_data (ply_pixel_buffer_t *buffer,
                                         ply_rectangle_t    *fill_area,
                                         uint32_t           *data)
 {
-        ply_pixel_buffer_fill_with_argb32_data_at_opacity_with_clip (buffer,
-                                                                     fill_area,
-                                                                     NULL,
-                                                                     data, 1.0);
+        ply_pixel_buffer_fill_with_argb32_data_at_opacity_with_clip_and_scale (buffer,
+                                                                               fill_area,
+                                                                               NULL,
+                                                                               data, 1.0, 1);
 }
 
 void
@@ -607,10 +718,10 @@ ply_pixel_buffer_fill_with_argb32_data_with_clip (ply_pixel_buffer_t *buffer,
                                                   ply_rectangle_t    *clip_area,
                                                   uint32_t           *data)
 {
-        ply_pixel_buffer_fill_with_argb32_data_at_opacity_with_clip (buffer,
-                                                                     fill_area,
-                                                                     clip_area,
-                                                                     data, 1.0);
+        ply_pixel_buffer_fill_with_argb32_data_at_opacity_with_clip_and_scale (buffer,
+                                                                               fill_area,
+                                                                               clip_area,
+                                                                               data, 1.0, 1);
 }
 
 static void
@@ -628,35 +739,6 @@ ply_pixel_buffer_copy_area (ply_pixel_buffer_t *canvas,
         }
 }
 
-static void
-ply_pixel_buffer_blend_area (ply_pixel_buffer_t *canvas,
-                            ply_pixel_buffer_t *source,
-                            int x, int y,
-                            ply_rectangle_t *cropped_area,
-                            double opacity)
-{
-        unsigned long row, column;
-        uint8_t opacity_as_byte = (uint8_t) (opacity * 255.0);
-
-        for (row = y; row < y + cropped_area->height; row++) {
-                for (column = x; column < x + cropped_area->width; column++) {
-                        uint32_t pixel_value;
-
-                        pixel_value = source->bytes[row * source->area.width + column];
-
-                        pixel_value = make_pixel_value_translucent (pixel_value, opacity_as_byte);
-
-                        if ((pixel_value >> 24) == 0x00)
-                                continue;
-
-                        ply_pixel_buffer_blend_value_at_pixel (canvas,
-                                                               cropped_area->x + (column - x),
-                                                               cropped_area->y + (row - y),
-                                                               pixel_value);
-                }
-        }
-}
-
 void
 ply_pixel_buffer_fill_with_buffer_at_opacity_with_clip (ply_pixel_buffer_t *canvas,
                                                         ply_pixel_buffer_t *source,
@@ -665,36 +747,51 @@ ply_pixel_buffer_fill_with_buffer_at_opacity_with_clip (ply_pixel_buffer_t *canv
                                                         ply_rectangle_t    *clip_area,
                                                         float               opacity)
 {
-        ply_rectangle_t cropped_area;
+        ply_rectangle_t fill_area;
         unsigned long x;
         unsigned long y;
 
         assert (canvas != NULL);
         assert (source != NULL);
 
-        cropped_area.x = x_offset;
-        cropped_area.y = y_offset;
-        cropped_area.width = source->area.width;
-        cropped_area.height = source->area.height;
+        /* Fast path to memcpy if we need no blending or scaling */
+        if (opacity == 1.0 && ply_pixel_buffer_is_opaque (source) &&
+            canvas->device_scale == source->device_scale) {
+                ply_rectangle_t cropped_area;
 
-        ply_pixel_buffer_crop_area_to_clip_area (canvas, &cropped_area, &cropped_area);
+                cropped_area.x = x_offset;
+                cropped_area.y = y_offset;
+                cropped_area.width = source->logical_area.width;
+                cropped_area.height = source->logical_area.height;
 
-        if (clip_area)
-                ply_rectangle_intersect (&cropped_area, clip_area, &cropped_area);
+                ply_pixel_buffer_crop_area_to_clip_area (canvas, &cropped_area, &cropped_area);
 
-        if (cropped_area.width == 0 || cropped_area.height == 0)
-                return;
+                /* clip_area is in source device pixels, which are also canvas device pixels */
+                if (clip_area)
+                        ply_rectangle_intersect (&cropped_area, clip_area, &cropped_area);
+
+                if (cropped_area.width == 0 || cropped_area.height == 0)
+                        return;
 
-        x = cropped_area.x - x_offset;
-        y = cropped_area.y - y_offset;
+                x = cropped_area.x - x_offset;
+                y = cropped_area.y - y_offset;
 
-        if (opacity == 1.0 && ply_pixel_buffer_is_opaque (source))
                 ply_pixel_buffer_copy_area (canvas, source, x, y, &cropped_area);
-        else
-                ply_pixel_buffer_blend_area (canvas, source, x, y, &cropped_area,
-                                             opacity);
 
-        ply_region_add_rectangle (canvas->updated_areas, &cropped_area);
+                ply_region_add_rectangle (canvas->updated_areas, &cropped_area);
+        } else {
+                fill_area.x = x_offset * source->device_scale;
+                fill_area.y = y_offset * source->device_scale;
+                fill_area.width = source->area.width;
+                fill_area.height = source->area.height;
+
+                ply_pixel_buffer_fill_with_argb32_data_at_opacity_with_clip_and_scale (canvas,
+                                                                                       &fill_area,
+                                                                                       clip_area,
+                                                                                       source->bytes,
+                                                                                       opacity,
+                                                                                       source->device_scale);
+        }
 }
 
 void
@@ -752,52 +849,15 @@ ply_pixel_buffer_interpolate (ply_pixel_buffer_t *buffer,
                               double              x,
                               double              y)
 {
-        int ix;
-        int iy;
         int width;
         int height;
-
-        int i;
-
-        int offset_x;
-        int offset_y;
-        uint32_t pixels[2][2];
-        uint32_t reply = 0;
         uint32_t *bytes;
 
         width = buffer->area.width;
         height = buffer->area.height;
-
-
         bytes = ply_pixel_buffer_get_argb32_data (buffer);
 
-        for (offset_y = 0; offset_y < 2; offset_y++) {
-                for (offset_x = 0; offset_x < 2; offset_x++) {
-                        ix = x + offset_x;
-                        iy = y + offset_y;
-
-                        if (ix < 0 || ix >= width || iy < 0 || iy >= height)
-                                pixels[offset_y][offset_x] = 0x00000000;
-                        else
-                                pixels[offset_y][offset_x] = bytes[ix + iy * width];
-                }
-        }
-        if (!pixels[0][0] && !pixels[0][1] && !pixels[1][0] && !pixels[1][1]) return 0;
-
-        ix = x;
-        iy = y;
-        x -= ix;
-        y -= iy;
-        for (i = 0; i < 4; i++) {
-                uint32_t value = 0;
-                uint32_t mask = 0xFF << (i * 8);
-                value += ((pixels[0][0]) & mask) * (1 - x) * (1 - y);
-                value += ((pixels[0][1]) & mask) * x * (1 - y);
-                value += ((pixels[1][0]) & mask) * (1 - x) * y;
-                value += ((pixels[1][1]) & mask) * x * y;
-                reply |= value & mask;
-        }
-        return reply;
+        return ply_pixels_interpolate (bytes, width, height, x, y);
 }
 
 ply_pixel_buffer_t *
@@ -908,4 +968,20 @@ ply_pixel_buffer_tile (ply_pixel_buffer_t *old_buffer,
         return buffer;
 }
 
+int
+ply_pixel_buffer_get_device_scale (ply_pixel_buffer_t *buffer)
+{
+        return buffer->device_scale;
+}
+
+void
+ply_pixel_buffer_set_device_scale (ply_pixel_buffer_t *buffer,
+                                   int                 scale)
+{
+        buffer->device_scale = scale;
+
+        buffer->logical_area.width = buffer->area.width / scale;
+        buffer->logical_area.height = buffer->area.height / scale;
+}
+
 /* vim: set ts=4 sw=4 et ai ci cino={.5s,^-2,+.5s,t0,g0,e-2,n-2,p2s,(0,=.5s,:.5s */
index a374c6c5e40b5252445aca7ae794e2ccb3330b70..595e9bd09c76c22c4f09d26d8147d2cf1b62a7ff 100644 (file)
@@ -43,6 +43,9 @@ ply_pixel_buffer_t *ply_pixel_buffer_new (unsigned long width,
 void ply_pixel_buffer_free (ply_pixel_buffer_t *buffer);
 void ply_pixel_buffer_get_size (ply_pixel_buffer_t *buffer,
                                 ply_rectangle_t    *size);
+int  ply_pixel_buffer_get_device_scale (ply_pixel_buffer_t *buffer);
+void ply_pixel_buffer_set_device_scale (ply_pixel_buffer_t *buffer,
+                                        int                 scale);
 
 unsigned long ply_pixel_buffer_get_width (ply_pixel_buffer_t *buffer);
 unsigned long ply_pixel_buffer_get_height (ply_pixel_buffer_t *buffer);
@@ -90,6 +93,12 @@ void ply_pixel_buffer_fill_with_argb32_data_at_opacity_with_clip (ply_pixel_buff
                                                                   ply_rectangle_t    *clip_area,
                                                                   uint32_t           *data,
                                                                   double              opacity);
+void ply_pixel_buffer_fill_with_argb32_data_at_opacity_with_clip_and_scale (ply_pixel_buffer_t *buffer,
+                                                                            ply_rectangle_t    *fill_area,
+                                                                            ply_rectangle_t    *clip_area,
+                                                                            uint32_t           *data,
+                                                                            double              opacity,
+                                                                            int                 scale);
 
 void ply_pixel_buffer_fill_with_buffer_at_opacity_with_clip (ply_pixel_buffer_t *canvas,
                                                              ply_pixel_buffer_t *source,