| 1 | // Aseprite Document Library |
| 2 | // Copyright (c) 2019 Igara Studio S.A. |
| 3 | // Copyright (c) 2001-2018 David Capello |
| 4 | // |
| 5 | // This file is released under the terms of the MIT license. |
| 6 | // Read LICENSE.txt for more information. |
| 7 | |
| 8 | #ifdef HAVE_CONFIG_H |
| 9 | #include "config.h" |
| 10 | #endif |
| 11 | |
| 12 | #include "doc/algorithm/resize_image.h" |
| 13 | |
| 14 | #include "doc/algorithm/rotsprite.h" |
| 15 | #include "doc/image_impl.h" |
| 16 | #include "doc/palette.h" |
| 17 | #include "doc/primitives_fast.h" |
| 18 | #include "doc/rgbmap.h" |
| 19 | #include "gfx/point.h" |
| 20 | |
| 21 | #include <cmath> |
| 22 | |
| 23 | namespace doc { |
| 24 | namespace algorithm { |
| 25 | |
| 26 | template<typename ImageTraits> |
| 27 | void resize_image_nearest(const Image* src, Image* dst) |
| 28 | { |
| 29 | double x_ratio = double(src->width()) / double(dst->width()); |
| 30 | double y_ratio = double(src->height()) / double(dst->height()); |
| 31 | double px, py; |
| 32 | |
| 33 | LockImageBits<ImageTraits> dstBits(dst); |
| 34 | auto dstIt = dstBits.begin(); |
| 35 | |
| 36 | for (int y=0; y<dst->height(); ++y) { |
| 37 | py = std::floor(y * y_ratio); |
| 38 | for (int x=0; x<dst->width(); ++x, ++dstIt) { |
| 39 | px = std::floor(x * x_ratio); |
| 40 | *dstIt = get_pixel_fast<ImageTraits>(src, int(px), int(py)); |
| 41 | } |
| 42 | } |
| 43 | } |
| 44 | |
| 45 | void resize_image(const Image* src, |
| 46 | Image* dst, |
| 47 | const ResizeMethod method, |
| 48 | const Palette* pal, |
| 49 | const RgbMap* rgbmap, |
| 50 | const color_t maskColor) |
| 51 | { |
| 52 | switch (method) { |
| 53 | |
| 54 | // TODO optimize this |
| 55 | case RESIZE_METHOD_NEAREST_NEIGHBOR: { |
| 56 | ASSERT(src->pixelFormat() == dst->pixelFormat()); |
| 57 | |
| 58 | switch (src->pixelFormat()) { |
| 59 | case IMAGE_RGB: resize_image_nearest<RgbTraits>(src, dst); break; |
| 60 | case IMAGE_GRAYSCALE: resize_image_nearest<GrayscaleTraits>(src, dst); break; |
| 61 | case IMAGE_INDEXED: resize_image_nearest<IndexedTraits>(src, dst); break; |
| 62 | case IMAGE_BITMAP: resize_image_nearest<BitmapTraits>(src, dst); break; |
| 63 | } |
| 64 | break; |
| 65 | } |
| 66 | |
| 67 | // TODO optimize this |
| 68 | case RESIZE_METHOD_BILINEAR: { |
| 69 | uint32_t color[4], dst_color = 0; |
| 70 | double u, v, du, dv; |
| 71 | int u_floor, u_floor2; |
| 72 | int v_floor, v_floor2; |
| 73 | int x, y; |
| 74 | |
| 75 | // We cannot do interpolations between RGB values on indexed |
| 76 | // images without a palette/rgbmap. |
| 77 | if (dst->pixelFormat() == IMAGE_INDEXED && |
| 78 | (!pal || !rgbmap)) { |
| 79 | resize_image( |
| 80 | src, dst, |
| 81 | RESIZE_METHOD_NEAREST_NEIGHBOR, |
| 82 | pal, rgbmap, maskColor); |
| 83 | return; |
| 84 | } |
| 85 | |
| 86 | u = v = 0.0; |
| 87 | du = (src->width()-1) * 1.0 / (dst->width()-1); |
| 88 | dv = (src->height()-1) * 1.0 / (dst->height()-1); |
| 89 | for (y=0; y<dst->height(); ++y) { |
| 90 | for (x=0; x<dst->width(); ++x) { |
| 91 | u_floor = (int)std::floor(u); |
| 92 | v_floor = (int)std::floor(v); |
| 93 | |
| 94 | if (u_floor > src->width()-1) { |
| 95 | u_floor = src->width()-1; |
| 96 | u_floor2 = src->width()-1; |
| 97 | } |
| 98 | else if (u_floor == src->width()-1) |
| 99 | u_floor2 = u_floor; |
| 100 | else |
| 101 | u_floor2 = u_floor+1; |
| 102 | |
| 103 | if (v_floor > src->height()-1) { |
| 104 | v_floor = src->height()-1; |
| 105 | v_floor2 = src->height()-1; |
| 106 | } |
| 107 | else if (v_floor == src->height()-1) |
| 108 | v_floor2 = v_floor; |
| 109 | else |
| 110 | v_floor2 = v_floor+1; |
| 111 | |
| 112 | // get the four colors |
| 113 | color[0] = src->getPixel(u_floor, v_floor); |
| 114 | color[1] = src->getPixel(u_floor2, v_floor); |
| 115 | color[2] = src->getPixel(u_floor, v_floor2); |
| 116 | color[3] = src->getPixel(u_floor2, v_floor2); |
| 117 | |
| 118 | // calculate the interpolated color |
| 119 | double u1 = u - u_floor; |
| 120 | double v1 = v - v_floor; |
| 121 | double u2 = 1 - u1; |
| 122 | double v2 = 1 - v1; |
| 123 | |
| 124 | switch (dst->pixelFormat()) { |
| 125 | case IMAGE_RGB: { |
| 126 | int r = int((rgba_getr(color[0])*u2 + rgba_getr(color[1])*u1)*v2 + |
| 127 | (rgba_getr(color[2])*u2 + rgba_getr(color[3])*u1)*v1); |
| 128 | int g = int((rgba_getg(color[0])*u2 + rgba_getg(color[1])*u1)*v2 + |
| 129 | (rgba_getg(color[2])*u2 + rgba_getg(color[3])*u1)*v1); |
| 130 | int b = int((rgba_getb(color[0])*u2 + rgba_getb(color[1])*u1)*v2 + |
| 131 | (rgba_getb(color[2])*u2 + rgba_getb(color[3])*u1)*v1); |
| 132 | int a = int((rgba_geta(color[0])*u2 + rgba_geta(color[1])*u1)*v2 + |
| 133 | (rgba_geta(color[2])*u2 + rgba_geta(color[3])*u1)*v1); |
| 134 | dst_color = rgba(r, g, b, a); |
| 135 | break; |
| 136 | } |
| 137 | case IMAGE_GRAYSCALE: { |
| 138 | int v = int((graya_getv(color[0])*u2 + graya_getv(color[1])*u1)*v2 + |
| 139 | (graya_getv(color[2])*u2 + graya_getv(color[3])*u1)*v1); |
| 140 | int a = int((graya_geta(color[0])*u2 + graya_geta(color[1])*u1)*v2 + |
| 141 | (graya_geta(color[2])*u2 + graya_geta(color[3])*u1)*v1); |
| 142 | dst_color = graya(v, a); |
| 143 | break; |
| 144 | } |
| 145 | case IMAGE_INDEXED: { |
| 146 | // Convert index to RGBA values |
| 147 | for (int i=0; i<4; ++i) { |
| 148 | if (color[i] == maskColor) |
| 149 | color[i] = pal->getEntry(color[i]) & rgba_rgb_mask; // Set alpha = 0 |
| 150 | else |
| 151 | color[i] = pal->getEntry(color[i]); |
| 152 | } |
| 153 | |
| 154 | int r = int((rgba_getr(color[0])*u2 + rgba_getr(color[1])*u1)*v2 + |
| 155 | (rgba_getr(color[2])*u2 + rgba_getr(color[3])*u1)*v1); |
| 156 | int g = int((rgba_getg(color[0])*u2 + rgba_getg(color[1])*u1)*v2 + |
| 157 | (rgba_getg(color[2])*u2 + rgba_getg(color[3])*u1)*v1); |
| 158 | int b = int((rgba_getb(color[0])*u2 + rgba_getb(color[1])*u1)*v2 + |
| 159 | (rgba_getb(color[2])*u2 + rgba_getb(color[3])*u1)*v1); |
| 160 | int a = int((rgba_geta(color[0])*u2 + rgba_geta(color[1])*u1)*v2 + |
| 161 | (rgba_geta(color[2])*u2 + rgba_geta(color[3])*u1)*v1); |
| 162 | dst_color = rgbmap->mapColor(r, g, b, a); |
| 163 | break; |
| 164 | } |
| 165 | } |
| 166 | |
| 167 | dst->putPixel(x, y, dst_color); |
| 168 | u += du; |
| 169 | } |
| 170 | u = 0.0; |
| 171 | v += dv; |
| 172 | } |
| 173 | break; |
| 174 | } |
| 175 | |
| 176 | case RESIZE_METHOD_ROTSPRITE: { |
| 177 | rotsprite_image( |
| 178 | dst, src, nullptr, |
| 179 | 0, 0, |
| 180 | dst->width(), 0, |
| 181 | dst->width(), dst->height(), |
| 182 | 0, dst->height()); |
| 183 | break; |
| 184 | } |
| 185 | |
| 186 | } |
| 187 | } |
| 188 | |
| 189 | void fixup_image_transparent_colors(Image* image) |
| 190 | { |
| 191 | int x, y; |
| 192 | |
| 193 | switch (image->pixelFormat()) { |
| 194 | |
| 195 | case IMAGE_RGB: { |
| 196 | int r, g, b, count; |
| 197 | LockImageBits<RgbTraits> bits(image); |
| 198 | LockImageBits<RgbTraits>::iterator it = bits.begin(); |
| 199 | |
| 200 | for (y=0; y<image->height(); ++y) { |
| 201 | for (x=0; x<image->width(); ++x, ++it) { |
| 202 | uint32_t c = *it; |
| 203 | |
| 204 | // if this is a completely-transparent pixel... |
| 205 | if (rgba_geta(c) == 0) { |
| 206 | count = 0; |
| 207 | r = g = b = 0; |
| 208 | |
| 209 | gfx::Rect area = gfx::Rect(x-1, y-1, 3, 3).createIntersection(image->bounds()); |
| 210 | LockImageBits<RgbTraits>::iterator it2 = bits.begin_area(area); |
| 211 | LockImageBits<RgbTraits>::iterator end2 = bits.end_area(area); |
| 212 | |
| 213 | for (; it2 != end2; ++it2) { |
| 214 | c = *it2; |
| 215 | if (rgba_geta(c) > 0) { |
| 216 | r += rgba_getr(c); |
| 217 | g += rgba_getg(c); |
| 218 | b += rgba_getb(c); |
| 219 | ++count; |
| 220 | } |
| 221 | } |
| 222 | |
| 223 | if (count > 0) { |
| 224 | r /= count; |
| 225 | g /= count; |
| 226 | b /= count; |
| 227 | *it = rgba(r, g, b, 0); |
| 228 | } |
| 229 | } |
| 230 | } |
| 231 | } |
| 232 | break; |
| 233 | } |
| 234 | |
| 235 | case IMAGE_GRAYSCALE: { |
| 236 | int k, count; |
| 237 | LockImageBits<GrayscaleTraits> bits(image); |
| 238 | LockImageBits<GrayscaleTraits>::iterator it = bits.begin(); |
| 239 | |
| 240 | for (y=0; y<image->height(); ++y) { |
| 241 | for (x=0; x<image->width(); ++x, ++it) { |
| 242 | uint16_t c = *it; |
| 243 | |
| 244 | // If this is a completely-transparent pixel... |
| 245 | if (graya_geta(c) == 0) { |
| 246 | count = 0; |
| 247 | k = 0; |
| 248 | |
| 249 | gfx::Rect area = gfx::Rect(x-1, y-1, 3, 3).createIntersection(image->bounds()); |
| 250 | LockImageBits<GrayscaleTraits>::iterator it2 = bits.begin_area(area); |
| 251 | LockImageBits<GrayscaleTraits>::iterator end2 = bits.end_area(area); |
| 252 | |
| 253 | for (; it2 != end2; ++it2) { |
| 254 | c = *it2; |
| 255 | if (graya_geta(c) > 0) { |
| 256 | k += graya_getv(c); |
| 257 | ++count; |
| 258 | } |
| 259 | } |
| 260 | |
| 261 | if (count > 0) { |
| 262 | k /= count; |
| 263 | *it = graya(k, 0); |
| 264 | } |
| 265 | } |
| 266 | } |
| 267 | } |
| 268 | break; |
| 269 | } |
| 270 | |
| 271 | } |
| 272 | } |
| 273 | |
| 274 | } // namespace algorithm |
| 275 | } // namespace doc |
| 276 | |