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             warning("NinePatchSide::init(): Bad pixel at %d,%d", (vertical ? 0 : i), (vertical ? i : 0));
00084             return false;
00085         }
00086 
00087         if (z != zz) {
00088             if (s != -1) {
00089                 NinePatchMark *mrk = new NinePatchMark;
00090 
00091                 mrk->offset = s;
00092                 mrk->length = i - s;
00093                 if (z == 0) {
00094                     mrk->ratio = 1;
00095                     t += mrk->length;
00096                 } else {
00097                     mrk->ratio = 0;
00098                 }
00099                 _m.push_back(mrk);
00100             }
00101             s = i;
00102             z = zz;
00103         }
00104     }
00105 
00106     _fix = len - 2 - t;
00107     for (i = 0; i < _m.size(); ++i) {
00108         if (_m[i]->ratio)
00109             _m[i]->ratio = _m[i]->length / (float)t;
00110     }
00111 
00112     return true;
00113 }
00114 
00115 void NinePatchSide::calcOffsets(int len) {
00116     uint i, j;
00117     int dest_offset = 0;
00118     int remaining_stretch = len - _fix;
00119 
00120     for (i = 0, j = 0; i < _m.size(); ++i) {
00121         _m[i]->dest_offset = dest_offset;
00122         if (_m[i]->ratio == 0) {
00123             _m[i]->dest_length = _m[i]->length;
00124         } else {
00125             _m[i]->dest_length = (len - _fix) * _m[i]->ratio;
00126             remaining_stretch -= _m[i]->dest_length;
00127             j = i;
00128         }
00129 
00130         dest_offset += _m[i]->dest_length;
00131     }
00132 
00133     if (remaining_stretch && _m.size()) {
00134         _m[j]->dest_length += remaining_stretch;
00135         if (j + 1 < _m.size())
00136             _m[j + 1]->dest_offset += remaining_stretch;
00137     }
00138 }
00139 
00140 NinePatchBitmap::NinePatchBitmap(Graphics::TransparentSurface *bmp, bool owns_bitmap) {
00141     int i;
00142     uint8 r, g, b, a;
00143 
00144     _bmp = bmp;
00145     _destroy_bmp = owns_bitmap;
00146     _h._m.clear();
00147     _v._m.clear();
00148     _cached_dw = 0;
00149     _cached_dh = 0;
00150     _width = bmp->w - 2;
00151     _height = bmp->h - 2;
00152 
00153     if (_width <= 0 || _height <= 0)
00154         goto bad_bitmap;
00155 
00156     /* make sure all four corners are transparent */
00157 #define _check_pixel(x, y) \
00158     bmp->format.colorToARGB(*(uint32 *)bmp->getBasePtr(x, y), a, r, g, b); \
00159     if (a != 0 && r + g + b + a != 4) goto bad_bitmap;
00160 
00161     _check_pixel(0, 0);
00162     _check_pixel(bmp->w - 1, 0);
00163     _check_pixel(0, bmp->h - 1);
00164     _check_pixel(bmp->w - 1, bmp->h - 1);
00165 #undef _check_pixel
00166 
00167     _padding.top = _padding.right = _padding.bottom = _padding.left = -1;
00168 
00169     i = 1;
00170     while (i < bmp->w) {
00171         bmp->format.colorToARGB(*(uint32 *)bmp->getBasePtr(i, bmp->h - 1), a, r, g, b);
00172 
00173         if (r + g + b == 0 && a == 255) {
00174             if (_padding.left == -1)
00175                 _padding.left = i - 1;
00176             else if (_padding.right != -1)
00177                 goto bad_bitmap;
00178         } else if (a == 0 || r + g + b == 0) {
00179             if (_padding.left != -1 && _padding.right == -1)
00180                 _padding.right = bmp->w - i - 1;
00181         }
00182         ++i;
00183     }
00184 
00185     i = 1;
00186     while (i < bmp->h) {
00187         bmp->format.colorToARGB(*(uint32 *)bmp->getBasePtr(bmp->w - 1, i), a, r, g, b);
00188 
00189         if (r + g + b == 0 && a == 255) {
00190             if (_padding.top == -1)
00191                 _padding.top = i - 1;
00192             else if (_padding.bottom != -1)
00193                 goto bad_bitmap;
00194         } else if (a == 0 || r + g + b == 0) {
00195             if (_padding.top != -1 && _padding.bottom == -1)
00196                 _padding.bottom = bmp->h - i - 1;
00197         }
00198         ++i;
00199     }
00200 
00201     if (!_h.init(bmp, false) || !_v.init(bmp, true)) {
00202 bad_bitmap:
00203         warning("NinePatchBitmap::NinePatchBitmap(): Bad bitmap");
00204 
00205         _h._m.clear();
00206         _v._m.clear();
00207     }
00208 }
00209 
00210 void NinePatchBitmap::blit(Graphics::Surface &target, int dx, int dy, int dw, int dh, byte *palette, int numColors) {
00211     /* don't draw bitmaps that are smaller than the fixed area */
00212     if (dw < _h._fix || dh < _v._fix)
00213         return;
00214 
00215     /* if the bitmap is the same size as the origin, then draw it as-is */
00216     if (dw == _width && dh == _height) {
00217         Common::Rect r(1, 1, dw, dh);
00218 
00219         _bmp->blit(target, dx, dy, Graphics::FLIP_NONE, &r);
00220         return;
00221     }
00222 
00223     /* only recalculate the offsets if they have changed since the last draw */
00224     if (_cached_dw != dw || _cached_dh != dh) {
00225         _h.calcOffsets(dw);
00226         _v.calcOffsets(dh);
00227 
00228         _cached_dw = dw;
00229         _cached_dh = dh;
00230     }
00231 
00232     /* Handle CLUT8 */
00233     if (target.format.bytesPerPixel == 1) {
00234         if (!palette)
00235             error("NinePatchBitmap::blit(): Trying to blit into a surface with 8bpp, you need a palette.");
00236 
00237         Surface *srf = new Surface();
00238         srf->create(target.w, target.h, _bmp->format);
00239 
00240         drawRegions(*srf, dx, dy, dw, dh);
00241 
00242         //TODO: This can be further optimized by keeping the data between draws,
00243         // and using a unique identifier for each palette, so that it only gets
00244         // recalculated when the palette changes.
00245         _cached_colors.clear();
00246 
00247         for (uint i = 0; i < srf->w; ++i) {
00248             for (uint j = 0; j < srf->h; ++j) {
00249                 uint32 color = *(uint32*)srf->getBasePtr(i, j);
00250                 if (color > 0) {
00251                     *((byte *)target.getBasePtr(i, j)) = closestGrayscale(color, palette, numColors);
00252                 }
00253             }
00254         }
00255 
00256         srf->free();
00257         delete srf;
00258 
00259         return;
00260     }
00261 
00262     /* Else, draw regions normally */
00263     drawRegions(target, dx, dy, dw, dh);
00264 }
00265 
00266 NinePatchBitmap::~NinePatchBitmap() {
00267     if (_destroy_bmp) {
00268         _bmp->free();
00269         delete _bmp;
00270     }
00271 }
00272 
00273 void NinePatchBitmap::drawRegions(Graphics::Surface &target, int dx, int dy, int dw, int dh) {
00274     /* draw each region */
00275     for (uint i = 0; i < _v._m.size(); ++i) {
00276         for (uint j = 0; j < _h._m.size(); ++j) {
00277             Common::Rect r(_h._m[j]->offset, _v._m[i]->offset,
00278                         _h._m[j]->offset + _h._m[j]->length, _v._m[i]->offset + _v._m[i]->length);
00279 
00280             _bmp->blit(target, dx + _h._m[j]->dest_offset, dy + _v._m[i]->dest_offset,
00281                     Graphics::FLIP_NONE, &r, TS_ARGB((uint)255, (uint)255, (uint)255, (uint)255),
00282                     _h._m[j]->dest_length, _v._m[i]->dest_length);
00283         }
00284     }
00285 }
00286 
00287 void NinePatchBitmap::blitClip(Graphics::Surface &target, Common::Rect clip, int dx, int dy, int dw, int dh) {
00288     /* don't draw bitmaps that are smaller than the fixed area */
00289     if (dw < _h._fix || dh < _v._fix)
00290         return;
00291 
00292     /* if the bitmap is the same size as the origin, then draw it as-is */
00293     if (dw == _width && dh == _height) {
00294         Common::Rect r(1, 1, dw, dh);
00295 
00296         _bmp->blitClip(target, clip, dx, dy, Graphics::FLIP_NONE, &r);
00297         return;
00298     }
00299 
00300     /* only recalculate the offsets if they have changed since the last draw */
00301     if (_cached_dw != dw || _cached_dh != dh) {
00302         _h.calcOffsets(dw);
00303         _v.calcOffsets(dh);
00304 
00305         _cached_dw = dw;
00306         _cached_dh = dh;
00307     }
00308 
00309     /* draw each region */
00310     for (uint i = 0; i < _v._m.size(); ++i) {
00311         for (uint j = 0; j < _h._m.size(); ++j) {
00312             Common::Rect r(_h._m[j]->offset, _v._m[i]->offset,
00313                 _h._m[j]->offset + _h._m[j]->length, _v._m[i]->offset + _v._m[i]->length);
00314 
00315             _bmp->blitClip(target, clip, dx + _h._m[j]->dest_offset, dy + _v._m[i]->dest_offset,
00316                 Graphics::FLIP_NONE, &r, TS_ARGB((uint)255, (uint)255, (uint)255, (uint)255),
00317                 _h._m[j]->dest_length, _v._m[i]->dest_length);
00318         }
00319     }
00320 }
00321 
00322 byte NinePatchBitmap::getColorIndex(uint32 target, byte* palette) {
00323     byte *pal = palette;
00324     uint i = 0;
00325     uint32 color = TS_RGB(pal[0], pal[1], pal[2]);
00326     while (color != target) {
00327         i += 3;
00328         color = TS_RGB(pal[i], pal[i + 1], pal[i + 2]);
00329     }
00330     return (i / 3);
00331 }
00332 
00333 uint32 NinePatchBitmap::grayscale(uint32 color) {
00334     byte r, g, b;
00335     _bmp->format.colorToRGB(color, r, g, b);
00336     return grayscale(r, g, b);
00337 }
00338 
00339 uint32 NinePatchBitmap::grayscale(byte r, byte g, byte b) {
00340     return (0.29 * r + 0.58 * g + 0.11 * b) / 3;
00341 }
00342 
00343 static inline uint32 dist(uint32 a, uint32 b) {
00344     if (a > b)
00345         return (a - b);
00346 
00347     return b - a;
00348 }
00349 
00350 byte NinePatchBitmap::closestGrayscale(uint32 color, byte* palette, int paletteLength) {
00351     if (!_cached_colors.contains(color)) {
00352         byte target = grayscale(color);
00353         byte bestNdx = 0;
00354         byte bestColor = grayscale(palette[0], palette[1], palette[2]);
00355         for (int i = 1; i < paletteLength; ++i) {
00356             byte current = grayscale(palette[i * 3], palette[(i * 3) + 1], palette[(i * 3) + 2]);
00357             if (dist(target, bestColor) >= dist(target, current)) {
00358                 bestColor = current;
00359                 bestNdx = i;
00360             }
00361         }
00362         _cached_colors[color] = bestNdx;
00363     }
00364 
00365     return _cached_colors[color];
00366 }
00367 
00368 } // end of namespace Graphics


Generated on Sat May 30 2020 05:01:01 for ResidualVM by doxygen 1.7.1
curved edge   curved edge