summaryrefslogtreecommitdiffstats
path: root/grub-core/video/bitmap_scale.c
diff options
context:
space:
mode:
Diffstat (limited to 'grub-core/video/bitmap_scale.c')
-rw-r--r--grub-core/video/bitmap_scale.c515
1 files changed, 515 insertions, 0 deletions
diff --git a/grub-core/video/bitmap_scale.c b/grub-core/video/bitmap_scale.c
new file mode 100644
index 0000000..70c32f0
--- /dev/null
+++ b/grub-core/video/bitmap_scale.c
@@ -0,0 +1,515 @@
+/* bitmap_scale.c - Bitmap scaling. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2006,2007,2008,2009 Free Software Foundation, Inc.
+ *
+ * GRUB 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB 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 GRUB. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/mm.h>
+#include <grub/misc.h>
+#include <grub/video.h>
+#include <grub/bitmap.h>
+#include <grub/bitmap_scale.h>
+#include <grub/types.h>
+#include <grub/dl.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+/* Prototypes for module-local functions. */
+static grub_err_t scale_nn (struct grub_video_bitmap *dst,
+ struct grub_video_bitmap *src);
+static grub_err_t scale_bilinear (struct grub_video_bitmap *dst,
+ struct grub_video_bitmap *src);
+
+static grub_err_t
+verify_source_bitmap (struct grub_video_bitmap *src)
+{
+ /* Verify the simplifying assumptions. */
+ if (src == 0)
+ return grub_error (GRUB_ERR_BUG,
+ "null src bitmap in grub_video_bitmap_create_scaled");
+ if (src->mode_info.red_field_pos % 8 != 0
+ || src->mode_info.green_field_pos % 8 != 0
+ || src->mode_info.blue_field_pos % 8 != 0
+ || src->mode_info.reserved_field_pos % 8 != 0)
+ return grub_error (GRUB_ERR_BUG,
+ "src format not supported for scale");
+ if (src->mode_info.width == 0 || src->mode_info.height == 0)
+ return grub_error (GRUB_ERR_BUG,
+ "source bitmap has a zero dimension");
+ if (src->mode_info.bytes_per_pixel * 8 != src->mode_info.bpp)
+ return grub_error (GRUB_ERR_BUG,
+ "bitmap to scale has inconsistent Bpp and bpp");
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+grub_video_bitmap_scale (struct grub_video_bitmap *dst,
+ struct grub_video_bitmap *src,
+ enum grub_video_bitmap_scale_method scale_method)
+{
+ switch (scale_method)
+ {
+ case GRUB_VIDEO_BITMAP_SCALE_METHOD_FASTEST:
+ case GRUB_VIDEO_BITMAP_SCALE_METHOD_NEAREST:
+ return scale_nn (dst, src);
+ case GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST:
+ case GRUB_VIDEO_BITMAP_SCALE_METHOD_BILINEAR:
+ return scale_bilinear (dst, src);
+ default:
+ return grub_error (GRUB_ERR_BUG, "Invalid scale_method value");
+ }
+}
+
+/* This function creates a new scaled version of the bitmap SRC. The new
+ bitmap has dimensions DST_WIDTH by DST_HEIGHT. The scaling algorithm
+ is given by SCALE_METHOD. If an error is encountered, the return code is
+ not equal to GRUB_ERR_NONE, and the bitmap DST is either not created, or
+ it is destroyed before this function returns.
+
+ Supports only direct color modes which have components separated
+ into bytes (e.g., RGBA 8:8:8:8 or BGR 8:8:8 true color).
+ But because of this simplifying assumption, the implementation is
+ greatly simplified. */
+grub_err_t
+grub_video_bitmap_create_scaled (struct grub_video_bitmap **dst,
+ int dst_width, int dst_height,
+ struct grub_video_bitmap *src,
+ enum grub_video_bitmap_scale_method
+ scale_method)
+{
+ *dst = 0;
+
+ grub_err_t err = verify_source_bitmap(src);
+ if (err != GRUB_ERR_NONE)
+ return err;
+ if (dst_width <= 0 || dst_height <= 0)
+ return grub_error (GRUB_ERR_BUG,
+ "requested to scale to a size w/ a zero dimension");
+
+ /* Create the new bitmap. */
+ grub_err_t ret;
+ ret = grub_video_bitmap_create (dst, dst_width, dst_height,
+ src->mode_info.blit_format);
+ if (ret != GRUB_ERR_NONE)
+ return ret; /* Error. */
+
+ ret = grub_video_bitmap_scale (*dst, src, scale_method);
+
+ if (ret == GRUB_ERR_NONE)
+ {
+ /* Success: *dst is now a pointer to the scaled bitmap. */
+ return GRUB_ERR_NONE;
+ }
+ else
+ {
+ /* Destroy the bitmap and return the error code. */
+ grub_video_bitmap_destroy (*dst);
+ *dst = 0;
+ return ret;
+ }
+}
+
+static grub_err_t
+make_h_align (unsigned *x, unsigned *w, unsigned new_w,
+ grub_video_bitmap_h_align_t h_align)
+{
+ grub_err_t ret = GRUB_ERR_NONE;
+ if (new_w >= *w)
+ {
+ *x = 0;
+ *w = new_w;
+ return GRUB_ERR_NONE;
+ }
+ switch (h_align)
+ {
+ case GRUB_VIDEO_BITMAP_H_ALIGN_LEFT:
+ *x = 0;
+ break;
+ case GRUB_VIDEO_BITMAP_H_ALIGN_CENTER:
+ *x = (*w - new_w) / 2;
+ break;
+ case GRUB_VIDEO_BITMAP_H_ALIGN_RIGHT:
+ *x = *w - new_w;
+ break;
+ default:
+ ret = grub_error (GRUB_ERR_BUG, "Invalid h_align value");
+ break;
+ }
+ *w = new_w;
+ return ret;
+}
+
+static grub_err_t
+make_v_align (unsigned *y, unsigned *h, unsigned new_h,
+ grub_video_bitmap_v_align_t v_align)
+{
+ grub_err_t ret = GRUB_ERR_NONE;
+ if (new_h >= *h)
+ {
+ *y = 0;
+ *h = new_h;
+ return GRUB_ERR_NONE;
+ }
+ switch (v_align)
+ {
+ case GRUB_VIDEO_BITMAP_V_ALIGN_TOP:
+ *y = 0;
+ break;
+ case GRUB_VIDEO_BITMAP_V_ALIGN_CENTER:
+ *y = (*h - new_h) / 2;
+ break;
+ case GRUB_VIDEO_BITMAP_V_ALIGN_BOTTOM:
+ *y = *h - new_h;
+ break;
+ default:
+ ret = grub_error (GRUB_ERR_BUG, "Invalid v_align value");
+ break;
+ }
+ *h = new_h;
+ return ret;
+}
+
+grub_err_t
+grub_video_bitmap_scale_proportional (struct grub_video_bitmap **dst,
+ int dst_width, int dst_height,
+ struct grub_video_bitmap *src,
+ enum grub_video_bitmap_scale_method
+ scale_method,
+ grub_video_bitmap_selection_method_t
+ selection_method,
+ grub_video_bitmap_v_align_t v_align,
+ grub_video_bitmap_h_align_t h_align)
+{
+ *dst = 0;
+ grub_err_t ret = verify_source_bitmap(src);
+ if (ret != GRUB_ERR_NONE)
+ return ret;
+ if (dst_width <= 0 || dst_height <= 0)
+ return grub_error (GRUB_ERR_BUG,
+ "requested to scale to a size w/ a zero dimension");
+
+ ret = grub_video_bitmap_create (dst, dst_width, dst_height,
+ src->mode_info.blit_format);
+ if (ret != GRUB_ERR_NONE)
+ return ret; /* Error. */
+
+ unsigned dx0 = 0;
+ unsigned dy0 = 0;
+ unsigned dw = dst_width;
+ unsigned dh = dst_height;
+ unsigned sx0 = 0;
+ unsigned sy0 = 0;
+ unsigned sw = src->mode_info.width;
+ unsigned sh = src->mode_info.height;
+
+ switch (selection_method)
+ {
+ case GRUB_VIDEO_BITMAP_SELECTION_METHOD_CROP:
+ /* Comparing sw/sh VS dw/dh. */
+ if (sw * dh < dw * sh)
+ ret = make_v_align (&sy0, &sh, sw * dh / dw, v_align);
+ else
+ ret = make_h_align (&sx0, &sw, sh * dw / dh, h_align);
+ break;
+ case GRUB_VIDEO_BITMAP_SELECTION_METHOD_PADDING:
+ if (sw * dh < dw * sh)
+ ret = make_h_align (&dx0, &dw, sw * dh / sh, h_align);
+ else
+ ret = make_v_align (&dy0, &dh, sh * dw / sw, v_align);
+ break;
+ case GRUB_VIDEO_BITMAP_SELECTION_METHOD_FITWIDTH:
+ if (sw * dh < dw * sh)
+ ret = make_v_align (&sy0, &sh, sw * dh / dw, v_align);
+ else
+ ret = make_v_align (&dy0, &dh, sh * dw / sw, v_align);
+ break;
+ case GRUB_VIDEO_BITMAP_SELECTION_METHOD_FITHEIGHT:
+ if (sw * dh < dw * sh)
+ ret = make_h_align (&dx0, &dw, sw * dh / sh, h_align);
+ else
+ ret = make_h_align (&sx0, &sw, sh * dw / dh, h_align);
+ break;
+ default:
+ ret = grub_error (GRUB_ERR_BUG, "Invalid selection_method value");
+ break;
+ }
+
+ if (ret == GRUB_ERR_NONE)
+ {
+ /* Backup original data. */
+ int src_width_orig = src->mode_info.width;
+ int src_height_orig = src->mode_info.height;
+ grub_uint8_t *src_data_orig = src->data;
+ int dst_width_orig = (*dst)->mode_info.width;
+ int dst_height_orig = (*dst)->mode_info.height;
+ grub_uint8_t *dst_data_orig = (*dst)->data;
+
+ int dstride = (*dst)->mode_info.pitch;
+ int sstride = src->mode_info.pitch;
+ /* bytes_per_pixel is the same for both src and dst. */
+ int bytes_per_pixel = src->mode_info.bytes_per_pixel;
+
+ /* Crop src and dst. */
+ src->mode_info.width = sw;
+ src->mode_info.height = sh;
+ src->data = (grub_uint8_t *) src->data + sx0 * bytes_per_pixel
+ + sy0 * sstride;
+ (*dst)->mode_info.width = dw;
+ (*dst)->mode_info.height = dh;
+ (*dst)->data = (grub_uint8_t *) (*dst)->data + dx0 * bytes_per_pixel
+ + dy0 * dstride;
+
+ /* Scale our image. */
+ ret = grub_video_bitmap_scale (*dst, src, scale_method);
+
+ /* Restore original data. */
+ src->mode_info.width = src_width_orig;
+ src->mode_info.height = src_height_orig;
+ src->data = src_data_orig;
+ (*dst)->mode_info.width = dst_width_orig;
+ (*dst)->mode_info.height = dst_height_orig;
+ (*dst)->data = dst_data_orig;
+ }
+
+ if (ret == GRUB_ERR_NONE)
+ {
+ /* Success: *dst is now a pointer to the scaled bitmap. */
+ return GRUB_ERR_NONE;
+ }
+ else
+ {
+ /* Destroy the bitmap and return the error code. */
+ grub_video_bitmap_destroy (*dst);
+ *dst = 0;
+ return ret;
+ }
+}
+
+static grub_err_t
+verify_bitmaps (struct grub_video_bitmap *dst, struct grub_video_bitmap *src)
+{
+ /* Verify the simplifying assumptions. */
+ if (dst == 0 || src == 0)
+ return grub_error (GRUB_ERR_BUG, "null bitmap in scale function");
+ if (dst->mode_info.red_field_pos % 8 != 0
+ || dst->mode_info.green_field_pos % 8 != 0
+ || dst->mode_info.blue_field_pos % 8 != 0
+ || dst->mode_info.reserved_field_pos % 8 != 0)
+ return grub_error (GRUB_ERR_BUG,
+ "dst format not supported");
+ if (src->mode_info.red_field_pos % 8 != 0
+ || src->mode_info.green_field_pos % 8 != 0
+ || src->mode_info.blue_field_pos % 8 != 0
+ || src->mode_info.reserved_field_pos % 8 != 0)
+ return grub_error (GRUB_ERR_BUG,
+ "src format not supported");
+ if (dst->mode_info.red_field_pos != src->mode_info.red_field_pos
+ || dst->mode_info.red_mask_size != src->mode_info.red_mask_size
+ || dst->mode_info.green_field_pos != src->mode_info.green_field_pos
+ || dst->mode_info.green_mask_size != src->mode_info.green_mask_size
+ || dst->mode_info.blue_field_pos != src->mode_info.blue_field_pos
+ || dst->mode_info.blue_mask_size != src->mode_info.blue_mask_size
+ || dst->mode_info.reserved_field_pos !=
+ src->mode_info.reserved_field_pos
+ || dst->mode_info.reserved_mask_size !=
+ src->mode_info.reserved_mask_size)
+ return grub_error (GRUB_ERR_BUG,
+ "dst and src not compatible");
+ if (dst->mode_info.bytes_per_pixel != src->mode_info.bytes_per_pixel)
+ return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+ "dst and src not compatible");
+ if (dst->mode_info.width == 0 || dst->mode_info.height == 0
+ || src->mode_info.width == 0 || src->mode_info.height == 0)
+ return grub_error (GRUB_ERR_BUG, "bitmap has a zero dimension");
+
+ return GRUB_ERR_NONE;
+}
+
+/* Nearest neighbor bitmap scaling algorithm.
+
+ Copy the bitmap SRC to the bitmap DST, scaling the bitmap to fit the
+ dimensions of DST. This function uses the nearest neighbor algorithm to
+ interpolate the pixels.
+
+ Supports only direct color modes which have components separated
+ into bytes (e.g., RGBA 8:8:8:8 or BGR 8:8:8 true color).
+ But because of this simplifying assumption, the implementation is
+ greatly simplified. */
+static grub_err_t
+scale_nn (struct grub_video_bitmap *dst, struct grub_video_bitmap *src)
+{
+ grub_err_t err = verify_bitmaps(dst, src);
+ if (err != GRUB_ERR_NONE)
+ return err;
+
+ grub_uint8_t *ddata = dst->data;
+ grub_uint8_t *sdata = src->data;
+ unsigned dw = dst->mode_info.width;
+ unsigned dh = dst->mode_info.height;
+ unsigned sw = src->mode_info.width;
+ unsigned sh = src->mode_info.height;
+ int dstride = dst->mode_info.pitch;
+ int sstride = src->mode_info.pitch;
+ /* bytes_per_pixel is the same for both src and dst. */
+ int bytes_per_pixel = dst->mode_info.bytes_per_pixel;
+ unsigned dy, sy, ystep, yfrac, yover;
+ unsigned sx, xstep, xfrac, xover;
+ grub_uint8_t *dptr, *dline_end, *sline;
+
+ xstep = sw / dw;
+ xover = sw % dw;
+ ystep = sh / dh;
+ yover = sh % dh;
+
+ for (dy = 0, sy = 0, yfrac = 0; dy < dh; dy++, sy += ystep, yfrac += yover)
+ {
+ if (yfrac >= dh)
+ {
+ yfrac -= dh;
+ sy++;
+ }
+ dptr = ddata + dy * dstride;
+ dline_end = dptr + dw * bytes_per_pixel;
+ sline = sdata + sy * sstride;
+ for (sx = 0, xfrac = 0; dptr < dline_end; sx += xstep, xfrac += xover, dptr += bytes_per_pixel)
+ {
+ grub_uint8_t *sptr;
+ int comp;
+
+ if (xfrac >= dw)
+ {
+ xfrac -= dw;
+ sx++;
+ }
+
+ /* Get the address of the pixels in src and dst. */
+ sptr = sline + sx * bytes_per_pixel;
+
+ /* Copy the pixel color value. */
+ for (comp = 0; comp < bytes_per_pixel; comp++)
+ dptr[comp] = sptr[comp];
+ }
+ }
+ return GRUB_ERR_NONE;
+}
+
+/* Bilinear interpolation image scaling algorithm.
+
+ Copy the bitmap SRC to the bitmap DST, scaling the bitmap to fit the
+ dimensions of DST. This function uses the bilinear interpolation algorithm
+ to interpolate the pixels.
+
+ Supports only direct color modes which have components separated
+ into bytes (e.g., RGBA 8:8:8:8 or BGR 8:8:8 true color).
+ But because of this simplifying assumption, the implementation is
+ greatly simplified. */
+static grub_err_t
+scale_bilinear (struct grub_video_bitmap *dst, struct grub_video_bitmap *src)
+{
+ grub_err_t err = verify_bitmaps(dst, src);
+ if (err != GRUB_ERR_NONE)
+ return err;
+
+ grub_uint8_t *ddata = dst->data;
+ grub_uint8_t *sdata = src->data;
+ unsigned dw = dst->mode_info.width;
+ unsigned dh = dst->mode_info.height;
+ unsigned sw = src->mode_info.width;
+ unsigned sh = src->mode_info.height;
+ int dstride = dst->mode_info.pitch;
+ int sstride = src->mode_info.pitch;
+ /* bytes_per_pixel is the same for both src and dst. */
+ int bytes_per_pixel = dst->mode_info.bytes_per_pixel;
+ unsigned dy, syf, sy, ystep, yfrac, yover;
+ unsigned sxf, sx, xstep, xfrac, xover;
+ grub_uint8_t *dptr, *dline_end, *sline;
+
+ xstep = (sw << 8) / dw;
+ xover = (sw << 8) % dw;
+ ystep = (sh << 8) / dh;
+ yover = (sh << 8) % dh;
+
+ for (dy = 0, syf = 0, yfrac = 0; dy < dh; dy++, syf += ystep, yfrac += yover)
+ {
+ if (yfrac >= dh)
+ {
+ yfrac -= dh;
+ syf++;
+ }
+ sy = syf >> 8;
+ dptr = ddata + dy * dstride;
+ dline_end = dptr + dw * bytes_per_pixel;
+ sline = sdata + sy * sstride;
+ for (sxf = 0, xfrac = 0; dptr < dline_end; sxf += xstep, xfrac += xover, dptr += bytes_per_pixel)
+ {
+ grub_uint8_t *sptr;
+ int comp;
+
+ if (xfrac >= dw)
+ {
+ xfrac -= dw;
+ sxf++;
+ }
+
+ /* Get the address of the pixels in src and dst. */
+ sx = sxf >> 8;
+ sptr = sline + sx * bytes_per_pixel;
+
+ /* If we have enough space to do so, use bilinear interpolation.
+ Otherwise, fall back to nearest neighbor for this pixel. */
+ if (sx < sw - 1 && sy < sh - 1)
+ {
+ /* Do bilinear interpolation. */
+
+ /* Fixed-point .8 numbers representing the fraction of the
+ distance in the x (u) and y (v) direction within the
+ box of 4 pixels in the source. */
+ unsigned u = sxf & 0xff;
+ unsigned v = syf & 0xff;
+
+ for (comp = 0; comp < bytes_per_pixel; comp++)
+ {
+ /* Get the component's values for the
+ four source corner pixels. */
+ unsigned f00 = sptr[comp];
+ unsigned f10 = sptr[comp + bytes_per_pixel];
+ unsigned f01 = sptr[comp + sstride];
+ unsigned f11 = sptr[comp + sstride + bytes_per_pixel];
+
+ /* Count coeffecients. */
+ unsigned c00 = (256 - u) * (256 - v);
+ unsigned c10 = u * (256 - v);
+ unsigned c01 = (256 - u) * v;
+ unsigned c11 = u * v;
+
+ /* Interpolate. */
+ unsigned fxy = c00 * f00 + c01 * f01 + c10 * f10 + c11 * f11;
+ fxy = fxy >> 16;
+
+ dptr[comp] = fxy;
+ }
+ }
+ else
+ {
+ /* Fall back to nearest neighbor interpolation. */
+ /* Copy the pixel color value. */
+ for (comp = 0; comp < bytes_per_pixel; comp++)
+ dptr[comp] = sptr[comp];
+ }
+ }
+ }
+ return GRUB_ERR_NONE;
+}