| /* 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; |
| } |