ResidualVM logo ResidualVM website - Forums - Contact us BuildBot - Doxygen - Wiki curved edge

nine_patch.cpp

Go to the documentation of this file.
00001 /* ScummVM - Graphic Adventure Engine
00002  *
00003  * ScummVM is the legal property of its developers, whose names
00004  * are too numerous to list here. Please refer to the COPYRIGHT
00005  * file distributed with this source distribution.
00006  *
00007  * This program is free software; you can redistribute it and/or
00008  * modify it under the terms of the GNU General Public License
00009  * as published by the Free Software Foundation; either version 2
00010  * of the License, or (at your option) any later version.
00011  *
00012  * This program is distributed in the hope that it will be useful,
00013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015  * GNU General Public License for more details.
00016  *
00017  * You should have received a copy of the GNU General Public License
00018  * along with this program; if not, write to the Free Software
00019  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00020  */
00021 
00022 /* This code is based on Nine Patch code by Matthew Leverton
00023    taken from https://github.com/konforce/Allegro-Nine-Patch
00024 
00025    Copyright (C) 2011 Matthew Leverton
00026 
00027    Permission is hereby granted, free of charge, to any person obtaining a copy of
00028    this software and associated documentation files (the "Software"), to deal in
00029    the Software without restriction, including without limitation the rights to
00030    use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
00031    of the Software, and to permit persons to whom the Software is furnished to do
00032    so, subject to the following conditions:
00033 
00034    The above copyright notice and this permission notice shall be included in all
00035    copies or substantial portions of the Software.
00036 
00037    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
00038    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
00039    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
00040    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
00041    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
00042    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
00043    SOFTWARE.
00044  */
00045 
00046 
00047 #include "common/array.h"
00048 #include "graphics/transparent_surface.h"
00049 #include "graphics/nine_patch.h"
00050 
00051 #include "graphics/managed_surface.h"
00052 
00053 namespace Graphics {
00054 
00055 NinePatchSide::~NinePatchSide() {
00056     for (uint i = 0; i < _m.size(); i++)
00057         delete _m[i];
00058 
00059     _m.clear();
00060 }
00061 
00062 
00063 bool NinePatchSide::init(Graphics::TransparentSurface *bmp, bool vertical) {
00064     const uint len = vertical ? bmp->h : bmp->w;
00065     uint i;
00066     int s, t, z;
00067 
00068     _m.clear();
00069 
00070     for (i = 1, s = -1, t = 0, z = -1; i < len; ++i) {
00071         int zz;
00072         uint8 r, g, b, a;
00073         uint32 *color = vertical ? (uint32 *)bmp->getBasePtr(0, i) : (uint32 *)bmp->getBasePtr(i, 0);
00074         bmp->format.colorToARGB(*color, a, r, g, b);
00075 
00076         if (i == len - 1)
00077             zz = -1;
00078         else if (r == 0 && g == 0 && b == 0 && a == 255)
00079             zz = 0;
00080         else if (a == 0 || r + g + b + a == 255 * 4)
00081             zz = 1;
00082         else
00083             return false;
00084 
00085         if (z != zz) {
00086             if (s != -1) {
00087                 NinePatchMark *mrk = new NinePatchMark;
00088 
00089                 mrk->offset = s;
00090                 mrk->length = i - s;
00091                 if (z == 0) {
00092                     mrk->ratio = 1;
00093                     t += mrk->length;
00094                 } else {
00095                     mrk->ratio = 0;
00096                 }
00097                 _m.push_back(mrk);
00098             }
00099             s = i;
00100             z = zz;
00101         }
00102     }
00103 
00104     _fix = len - 2 - t;
00105     for (i = 0; i < _m.size(); ++i) {
00106         if (_m[i]->ratio)
00107             _m[i]->ratio = _m[i]->length / (float)t;
00108     }
00109 
00110     return true;
00111 }
00112 
00113 void NinePatchSide::calcOffsets(int len) {
00114     uint i, j;
00115     int dest_offset = 0;
00116     int remaining_stretch = len - _fix;
00117 
00118     for (i = 0, j = 0; i < _m.size(); ++i) {
00119         _m[i]->dest_offset = dest_offset;
00120         if (_m[i]->ratio == 0) {
00121             _m[i]->dest_length = _m[i]->length;
00122         } else {
00123             _m[i]->dest_length = (len - _fix) * _m[i]->ratio;
00124             remaining_stretch -= _m[i]->dest_length;
00125             j = i;
00126         }
00127 
00128         dest_offset += _m[i]->dest_length;
00129     }
00130 
00131     if (remaining_stretch) {
00132         _m[j]->dest_length += remaining_stretch;
00133         if (j + 1 < _m.size())
00134             _m[j + 1]->dest_offset += remaining_stretch;
00135     }
00136 }
00137 
00138 NinePatchBitmap::NinePatchBitmap(Graphics::TransparentSurface *bmp, bool owns_bitmap) {
00139     int i;
00140     uint8 r, g, b, a;
00141 
00142     _bmp = bmp;
00143     _destroy_bmp = owns_bitmap;
00144     _h._m.clear();
00145     _v._m.clear();
00146     _cached_dw = 0;
00147     _cached_dh = 0;
00148     _width = bmp->w - 2;
00149     _height = bmp->h - 2;
00150 
00151     if (_width <= 0 || _height <= 0)
00152         goto bad_bitmap;
00153 
00154     /* make sure all four corners are transparent */
00155 #define _check_pixel(x, y) \
00156     bmp->format.colorToARGB(*(uint32 *)bmp->getBasePtr(x, y), a, r, g, b); \
00157     if (a != 0 && r + g + b + a != 4) goto bad_bitmap;
00158 
00159     _check_pixel(0, 0);
00160     _check_pixel(bmp->w - 1, 0);
00161     _check_pixel(0, bmp->h - 1);
00162     _check_pixel(bmp->w - 1, bmp->h - 1);
00163 #undef _check_pixel
00164 
00165     _padding.top = _padding.right = _padding.bottom = _padding.left = -1;
00166 
00167     i = 1;
00168     while (i < bmp->w) {
00169         bmp->format.colorToARGB(*(uint32 *)bmp->getBasePtr(i, bmp->h - 1), a, r, g, b);
00170 
00171         if (r + g + b == 0 && a == 1) {
00172             if (_padding.left == -1)
00173                 _padding.left = i - 1;
00174             else if (_padding.right != -1)
00175                 goto bad_bitmap;
00176         } else if (a == 0 || r + g + b + a == 4) {
00177             if (_padding.left != -1 && _padding.right == -1)
00178                 _padding.right = bmp->w - i - 1;
00179         }
00180         ++i;
00181     }
00182 
00183     i = 1;
00184     while (i < bmp->h) {
00185         bmp->format.colorToARGB(*(uint32 *)bmp->getBasePtr(bmp->w - 1, i), a, r, g, b);
00186 
00187         if (r + g + b == 0 && a == 1) {
00188             if (_padding.top == -1)
00189                 _padding.top = i - 1;
00190             else if (_padding.bottom != -1)
00191                 goto bad_bitmap;
00192         } else if (a == 0 || r + g + b + a == 4) {
00193             if (_padding.top != -1 && _padding.bottom == -1)
00194                 _padding.bottom = bmp->h - i - 1;
00195         }
00196         ++i;
00197     }
00198 
00199     if (!_h.init(bmp, false) || !_v.init(bmp, true)) {
00200 bad_bitmap:
00201         _h._m.clear();
00202         _v._m.clear();
00203     }
00204 }
00205 
00206 void NinePatchBitmap::blit(Graphics::Surface &target, int dx, int dy, int dw, int dh, byte *palette, byte numColors) {
00207     /* don't draw bitmaps that are smaller than the fixed area */
00208     if (dw < _h._fix || dh < _v._fix)
00209         return;
00210 
00211     /* if the bitmap is the same size as the origin, then draw it as-is */
00212     if (dw == _width && dh == _height) {
00213         Common::Rect r(1, 1, dw, dh);
00214 
00215         _bmp->blit(target, dx, dy, Graphics::FLIP_NONE, &r);
00216         return;
00217     }
00218 
00219     /* only recalculate the offsets if they have changed since the last draw */
00220     if (_cached_dw != dw || _cached_dh != dh) {
00221         _h.calcOffsets(dw);
00222         _v.calcOffsets(dh);
00223 
00224         _cached_dw = dw;
00225         _cached_dh = dh;
00226     }
00227 
00228     /* Handle CLUT8 */
00229     if (target.format.bytesPerPixel == 1) {
00230         if (!palette)
00231             warning("Trying to blit into a surface with 1bpp, you need the palette.");
00232 
00233         Surface *srf = new Surface();
00234         srf->create(target.w, target.h, _bmp->format);
00235 
00236         drawRegions(*srf, dx, dy, dw, dh);
00237 
00238         //TODO: This can be further optimized by keeping the data between draws,
00239         // and using a unique identifier for each palette, so that it only gets
00240         // recalculated when the palette changes.
00241         _cached_colors.clear();
00242 
00243         for (uint i = 0; i < srf->w; ++i) {
00244             for (uint j = 0; j < srf->h; ++j) {
00245                 uint32 color = *(uint32*)srf->getBasePtr(i, j);
00246                 if (color > 0) {
00247                     *((byte *)target.getBasePtr(i, j)) = closestGrayscale(color, palette, numColors);
00248                 }
00249             }
00250         }
00251 
00252         srf->free();
00253         delete srf;
00254 
00255         return;
00256     }
00257 
00258     /* Else, draw regions normally */
00259     drawRegions(target, dx, dy, dw, dh);
00260 }
00261 
00262 NinePatchBitmap::~NinePatchBitmap() {
00263     if (_destroy_bmp) {
00264         _bmp->free();
00265         delete _bmp;
00266     }
00267 }
00268 
00269 void NinePatchBitmap::drawRegions(Graphics::Surface &target, int dx, int dy, int dw, int dh) {
00270     /* draw each region */
00271     for (uint i = 0; i < _v._m.size(); ++i) {
00272         for (uint j = 0; j < _h._m.size(); ++j) {
00273             Common::Rect r(_h._m[j]->offset, _v._m[i]->offset,
00274                         _h._m[j]->offset + _h._m[j]->length, _v._m[i]->offset + _v._m[i]->length);
00275 
00276             _bmp->blit(target, dx + _h._m[j]->dest_offset, dy + _v._m[i]->dest_offset,
00277                     Graphics::FLIP_NONE, &r, TS_ARGB(255, 255, 255, 255),
00278                     _h._m[j]->dest_length, _v._m[i]->dest_length);
00279         }
00280     }
00281 }
00282 
00283 void NinePatchBitmap::blitClip(Graphics::Surface &target, Common::Rect clip, int dx, int dy, int dw, int dh) {
00284     /* don't draw bitmaps that are smaller than the fixed area */
00285     if (dw < _h._fix || dh < _v._fix)
00286         return;
00287 
00288     /* if the bitmap is the same size as the origin, then draw it as-is */
00289     if (dw == _width && dh == _height) {
00290         Common::Rect r(1, 1, dw, dh);
00291 
00292         _bmp->blitClip(target, clip, dx, dy, Graphics::FLIP_NONE, &r);
00293         return;
00294     }
00295 
00296     /* only recalculate the offsets if they have changed since the last draw */
00297     if (_cached_dw != dw || _cached_dh != dh) {
00298         _h.calcOffsets(dw);
00299         _v.calcOffsets(dh);
00300 
00301         _cached_dw = dw;
00302         _cached_dh = dh;
00303     }
00304 
00305     /* draw each region */
00306     for (uint i = 0; i < _v._m.size(); ++i) {
00307         for (uint j = 0; j < _h._m.size(); ++j) {
00308             Common::Rect r(_h._m[j]->offset, _v._m[i]->offset,
00309                 _h._m[j]->offset + _h._m[j]->length, _v._m[i]->offset + _v._m[i]->length);
00310 
00311             _bmp->blitClip(target, clip, dx + _h._m[j]->dest_offset, dy + _v._m[i]->dest_offset,
00312                 Graphics::FLIP_NONE, &r, TS_ARGB(255, 255, 255, 255),
00313                 _h._m[j]->dest_length, _v._m[i]->dest_length);
00314         }
00315     }
00316 }
00317 
00318 byte NinePatchBitmap::getColorIndex(uint32 target, byte* palette) {
00319     byte *pal = palette;
00320     uint i = 0;
00321     uint32 color = TS_RGB(pal[0], pal[1], pal[2]);
00322     while (color != target) {
00323         i += 3;
00324         color = TS_RGB(pal[i], pal[i + 1], pal[i + 2]);
00325     }
00326     return (i / 3);
00327 }
00328 
00329 uint32 NinePatchBitmap::grayscale(uint32 color) {
00330     byte r, g, b;
00331     _bmp->format.colorToRGB(color, r, g, b);
00332     return grayscale(r, g, b);
00333 }
00334 
00335 uint32 NinePatchBitmap::grayscale(byte r, byte g, byte b) {
00336     return (0.29 * r + 0.58 * g + 0.11 * b) / 3;
00337 }
00338 
00339 static inline uint32 dist(uint32 a, uint32 b) {
00340     if (a > b)
00341         return (a - b);
00342 
00343     return b - a;
00344 }
00345 
00346 byte NinePatchBitmap::closestGrayscale(uint32 color, byte* palette, byte paletteLength) {
00347     if (!_cached_colors.contains(color)) {
00348         byte target = grayscale(color);
00349         byte bestNdx = 0;
00350         byte bestColor = grayscale(palette[0], palette[1], palette[2]);
00351         for (byte i = 1; i < paletteLength; ++i) {
00352             byte current = grayscale(palette[i * 3], palette[(i * 3) + 1], palette[(i * 3) + 2]);
00353             if (dist(target, bestColor) >= dist(target, current)) {
00354                 bestColor = current;
00355                 bestNdx = i;
00356             }
00357         }
00358         _cached_colors[color] = bestNdx;
00359     }
00360 
00361     return _cached_colors[color];
00362 }
00363 
00364 } // end of namespace Graphics


Generated on Sat May 25 2019 05:00:50 for ResidualVM by doxygen 1.7.1
curved edge   curved edge