/* * Bitmap to (Sierra) AGI picture resource converter * Copyright (C) 2012 Jarno Elonen * * * This program 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 2 of the License, or * (at your option) any later version. * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ #include "bmp2agipic.h" #include #include #include #include #include #include static const int AGI_WIDTH = 160; static const int AGI_HEIGHT = 168; static const int N_COLORS = 16; static const unsigned char COLOR_NONE = 255; const QColor ega[] = { QColor(0x00, 0x00, 0x00), // black QColor(0x00, 0x00, 0xA0), // blue QColor(0x00, 0xA0, 0x00), // green QColor(0x00, 0xA0, 0xA0), // cyan QColor(0xA0, 0x00, 0x00), // red QColor(0xA0, 0x00, 0xA0), // magenta QColor(0xA0, 0x50, 0x00), // brown QColor(0xA0, 0xA0, 0xA0), // light grey QColor(0x50, 0x50, 0x50), // dark grey QColor(0x50, 0x50, 0xFF), // light blue QColor(0x50, 0xFF, 0x50), // light green QColor(0x50, 0xFF, 0xFF), // light cyan QColor(0xFF, 0x50, 0x50), // light red QColor(0xFF, 0x50, 0xFF), // light magenta QColor(0xFF, 0xFF, 0x50), // yellow QColor(0xFF, 0xFF, 0xFF) // white }; typedef unsigned char AGIPic[AGI_WIDTH][AGI_HEIGHT]; struct Coord { Coord( int _x, int _y ) : x(_x), y(_y) {} int x, y; }; struct CoordColor { CoordColor( int _x, int _y, unsigned char _c ) : x(_x), y(_y), c(_c) {} int x, y; unsigned char c; }; // Quantize given image to EGA palette and reshape to AGI native AGI_WIDTHxAGI_HEIGHT void QuantizeAGI( const QImage& img, AGIPic& out ) { #define SQUARE(x) ((x)*(x)) assert(img.width() == AGI_WIDTH || img.width() == AGI_WIDTH*2 ); assert(img.height() >= AGI_HEIGHT ); int xstep = (img.width() == AGI_WIDTH) ? 1 : 2; for ( int y=0; y=AGI_WIDTH || y<0 || y>=AGI_HEIGHT ) return dummy; else return pic[x][y]; } // Returns true if neighborhood is solid, suitable for floodfill bool isOnFloodFillArea( AGIPic& pic, int x, int y ) { if ( agiPix(pic, x,y) == COLOR_NONE ) return false; for ( int dx=-1; dx<=1; ++dx ) for ( int dy=-1; dy<=1; ++dy ) if ( agiPix(pic, x,y) != agiPix(pic, x+dx,y+dy)) return false; return true; } bool has4NeighborOfColor( AGIPic& pic, int x, int y, unsigned char c ) { return \ (x0 && agiPix(pic,x-1,y+0) == c) || (y0 && agiPix(pic,x+0,y-1) == c); } // Trace how long a line continues to given direction (excluding starting pixel) int traceToDir( AGIPic& pic, int x, int y, int dx, int dy, unsigned char c, int max ) { assert(c != COLOR_NONE); // could cause infinite trace int cnt = -1; do { cnt++; if ( cnt >= max ) break; x+=dx; y+=dy; } while ( agiPix(pic,x, y) == c ); return cnt; } int count8NeighborOfColor( AGIPic& pic, int x, int y, unsigned char c ) { int res = 0; for ( int dx=-1; dx<=1; ++dx ) for ( int dy=-1; dy<=1; ++dy ) if ( dx!=0 || dy!=0 ) if ( agiPix(pic,x+dx,y+dy) == c ) res++; return res; } // Flood fill area with color COLOR_NONE, but // leave border pixels of previous COLOR_NONE areas // untouched so that we'll get proper fill borders later. void floodFillEmpty( AGIPic& pic, int x, int y ) { std::queue q; q.push(Coord(x,y)); const unsigned char visited = COLOR_NONE-1; const unsigned char src_color = pic[x][y]; assert( src_color != COLOR_NONE ); assert( src_color != visited ); while ( !q.empty()) { Coord c = q.front(); q.pop(); if ( agiPix(pic, c.x, c.y) == src_color && !has4NeighborOfColor(pic,c.x,c.y, COLOR_NONE )) { agiPix(pic, c.x, c.y) = visited; q.push(Coord(c.x+1, c.y+0)); q.push(Coord(c.x-1, c.y+0)); q.push(Coord(c.x+0, c.y+1)); q.push(Coord(c.x+0, c.y-1)); } } for (int y=0; y& singlePoints, Coord& minCoord ) { // Find pixel with the least neighbors of color i // (i.e. most probable end of line) int minCount = 0xFF; for (int y=y0; y singlePoints; while (true) // loop until no pixels of color i are found { Coord minCoord(-1,-1); int minCount = findBestLineStartFromArea( pic, 0,0, AGI_WIDTH,AGI_HEIGHT, color, singlePoints, minCoord ); if ( minCount == 0xFF ) // No more lines found break; newTrace: // Start tracing the line int x=minCoord.x, y=minCoord.y; assert( agiPix(pic,x,y) == color ); *res += char( 0xF7 ); // Relative line command *res += char( x ); // Starting x *res += char( y ); // Starting y agiPix(pic,x,y) = COLOR_NONE; // Trace a line while (true) { // Find out direction into which the line continues the longest int max=0, maxdx=0xFF,maxdy=0xFF; for ( int dx=-1; dx<=1; ++dx ) for ( int dy=-1; dy<=1; ++dy ) if ( dx != 0 || dy != 0 ) { int len = traceToDir( pic, x,y, dx,dy, color, AGI_WIDTH ); if ( len > max ) { max = len; maxdx = dx; maxdy = dy; } } if ( max==0 ) // Nowhere to go, end the trace { // Just for aesthetic reasons, try to start the next // line nearby the end of the last one minCount = findBestLineStartFromArea( pic, x-5,y-5, 10,10, color, singlePoints, minCoord ); if ( minCount == 0xFF ) break; // Not found, do a full pic scan else goto newTrace; // New line in the neighborhood, start from there } if ( max>6 ) // Clamp to maximum length of relative line max=6; int stepx = maxdx*max; int stepy = maxdy*max; assert(abs(stepx) <= 7); assert(abs(stepy) <= 7); // Write out the relative line stepping byte unsigned char chr = (((stepx<0)?1:0) << 7) | // x sign (abs(stepx) << 4) | // Xdisp (((stepy<0)?1:0) << 3) | // y sign (abs(stepy) << 0); // Ydisp assert((chr&0xF0) != 0xF0); // Must not be mistaken for a draw command *res += char(chr); // Clear the line while (max>=0) { agiPix(pic,x+maxdx*max,y+maxdy*max) = COLOR_NONE; --max; } // Move pointer to the new end x += stepx; y += stepy; } } // When all continuous lines are drawn, // write out single points as a brush/plot/pen operation if ( singlePoints.size()>0 ) { *res += char( 0xFA ); // Pen command for ( int i=0; i<(int)singlePoints.size(); ++i ) { assert( (unsigned char)singlePoints[i].x < 0xF0); assert( (unsigned char)singlePoints[i].y < 0xF0); *res += char( singlePoints[i].x ); *res += char( singlePoints[i].y ); } } } } // Convert either a visual or a priority image and write to res void oneChannelToAGIPicture( const QImage& chan, QByteArray* res, bool isPri ) { unsigned char pic[AGI_WIDTH][AGI_HEIGHT]; QuantizeAGI( chan, pic ); // We're doing one channel at a time, disable the other one first *res += char( isPri ? 0xF1 : 0xF3 ); // Clear the default color, no sense in filling/drawing with it: unsigned default_color = isPri ? 4 : 15; // 4=red for pri, 15=white for visual for (int y=0; y floodFills; for ( int c=N_COLORS-1; c>=0; --c ) for (int y=0; y