diff options
Diffstat (limited to 'grub-core/video/bitmap_scale.c')
-rw-r--r-- | grub-core/video/bitmap_scale.c | 515 |
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; +} |