/** \file dbitmap.c * Print to Bitmap */ /* XTrkCad - Model Railroad CAD * Copyright (C) 2005 Dave Bullis * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "custom.h" #include "dynstring.h" #include "fileio.h" #include "layout.h" #include "param.h" #include "paths.h" #include "track.h" #include "common-ui.h" #ifdef WIN32 #ifdef _WIN64 #define BITMAPDIM 50000 #define BITMAPSIZE 500e6 #else #define BITMAPDIM 32000 #define BITMAPSIZE 150e6 #endif #else // Not WIN #define BITMAPDIM 50000 #define BITMAPSIZE 500e6 #endif // WIN32 /** Option flags for bitmap export */ #define BITMAPDRAWTITLE 1 #define BITMAPDRAWFRAMEONLY (1<<1) #define BITMAPDRAWCENTERLINE (1<<2) #define BITMAPDRAWBACKGROUND (1<<3) #define POINTSTOINCH 72.0 #define BITMAPEXPORTFONTSIZE 18 // line height is 20 percent larger than fontsize and converted to inches #define LINEHEIGHT (BITMAPEXPORTFONTSIZE * 1.2 / POINTSTOINCH) #define DEFAULTMARGIN 0.2 #define LEFTMARGIN (DEFAULTMARGIN + 0.3) #define BOTTOMMARGIN (DEFAULTMARGIN + LINEHEIGHT) static long outputBitMapTogglesV = 3; static double outputBitMapDensity = 10; static struct wFilSel_t * bitmap_fs; static wWinPix_t bitmap_w, bitmap_h; static drawCmd_t bitmap_d = { NULL, &screenDrawFuncs, 0, 16.0, 0.0, {0.0, 0.0}, {1.0, 1.0}, Pix2CoOrd, CoOrd2Pix }; /** * Show string at given y position centered in x direction * * \param [in] string If non-null, the string. * \param font The font. * \param fontSize Size of the font. * \param yPos The position. */ static void DrawTextCenterXPosY( char *string, wFont_p font, wFontSize_t fontSize, POS_T yPos ) { coOrd textSize; coOrd p; DrawTextSize( &mainD, string, font, fontSize * bitmap_d.scale, FALSE, &textSize ); p.x = ( bitmap_d.size.x - textSize.x ) / 2.0 + bitmap_d.orig.x; p.y = mapD.size.y + yPos*bitmap_d.scale; DrawString( &bitmap_d, p, 0.0, string, font, fontSize*bitmap_d.scale, wDrawColorBlack ); } /** * Draw the product info to the bitmap * * \param [in] preFix preFix to add to product name * \param fontSize Size of the font. * \param yPos The position. */ static void DrawProductInfo( char *preFix, wFontSize_t fontSize, POS_T yPos ) { wFont_p fp, fp_bi; coOrd textsize, textsize1; coOrd textPos; fp = wStandardFont( F_TIMES, FALSE, FALSE ); fp_bi = wStandardFont( F_TIMES, TRUE, TRUE ); DrawTextSize( &mainD, preFix, fp, fontSize * bitmap_d.scale, FALSE, &textsize ); DrawTextSize( &mainD, sProdName, fp_bi, fontSize * bitmap_d.scale, FALSE, &textsize1 ); textPos.x = ( bitmap_d.size.x - ( textsize.x + textsize1.x ) ) / 2.0 + bitmap_d.orig.x; textPos.y = -LINEHEIGHT*bitmap_d.scale; DrawString( &bitmap_d, textPos, 0.0, preFix, fp, fontSize * bitmap_d.scale, wDrawColorBlack ); textPos.x += textsize.x; DrawString( &bitmap_d, textPos, 0.0, sProdName, fp_bi, fontSize * bitmap_d.scale, wDrawColorBlack ); } /** * Saves a bitmap file * * \param files number of files, must be 1 * \param [in] fileName name of the file * \param [in,out] data unused * * \returns true on success, false otherwise */ static int SaveBitmapFile( int files, char **fileName, void * data ) { bool result; CHECK( fileName != NULL ); CHECK( files == 1 ); wSetCursor( mainD.d, wCursorWait ); InfoMessage( _( "Drawing tracks to bitmap" ) ); SetCurrentPath( BITMAPPATHKEY, fileName[ 0 ] ); bitmap_d.d = wBitMapCreate( bitmap_w, bitmap_h, 8 ); if( !bitmap_d.d ) { NoticeMessage( MSG_WBITMAP_FAILED, _( "Ok" ), NULL ); return false; } if( outputBitMapTogglesV & ( BITMAPDRAWFRAMEONLY | BITMAPDRAWTITLE ) ) { coOrd p[4]; p[0].x = p[3].x = 0.0; p[1].x = p[2].x = mapD.size.x; p[0].y = p[1].y = 0.0; p[2].y = p[3].y = mapD.size.y; DrawPoly( &bitmap_d, 4, p, NULL, wDrawColorBlack, 2, DRAW_CLOSED ); if( ( outputBitMapTogglesV & BITMAPDRAWFRAMEONLY ) ) { DrawRuler( &bitmap_d, p[0], p[1], 0.0, TRUE, FALSE, wDrawColorBlack ); DrawRuler( &bitmap_d, p[0], p[3], 0.0, TRUE, TRUE, wDrawColorBlack ); DrawRuler( &bitmap_d, p[1], p[2], 0.0, FALSE, FALSE, wDrawColorBlack ); DrawRuler( &bitmap_d, p[3], p[2], 0.0, FALSE, TRUE, wDrawColorBlack ); //y0 = 0.37; //y1 = 0.2; } if( outputBitMapTogglesV & BITMAPDRAWTITLE ) { wFont_p fp; fp = wStandardFont( F_TIMES, FALSE, FALSE ); DrawTextCenterXPosY( GetLayoutTitle(), fp, BITMAPEXPORTFONTSIZE, 1.4 * LINEHEIGHT ); DrawTextCenterXPosY( GetLayoutSubtitle(), fp, BITMAPEXPORTFONTSIZE, 0.4 * LINEHEIGHT ); DrawProductInfo( N_( "Drawn with " ), BITMAPEXPORTFONTSIZE, -LINEHEIGHT ); } } wDrawClip( bitmap_d.d, ( wWinPix_t )( -bitmap_d.orig.x/bitmap_d.scale*bitmap_d.dpi ), ( wWinPix_t )( -bitmap_d.orig.y/bitmap_d.scale*bitmap_d.dpi ), ( wWinPix_t )( mapD.size.x/bitmap_d.scale*bitmap_d.dpi ), ( wWinPix_t )( mapD.size.y/bitmap_d.scale*bitmap_d.dpi ) ); DrawSnapGrid( &bitmap_d, mapD.size, TRUE ); if( outputBitMapTogglesV & BITMAPDRAWBACKGROUND && GetLayoutBackGroundScreen() < 100.0 ) { wWinPix_t bitmapPosX; wWinPix_t bitmapPosY; wWinPix_t bitmapWidth; TranslateBackground( &bitmap_d, bitmap_d.orig.x, bitmap_d.orig.y, &bitmapPosX, &bitmapPosY, &bitmapWidth ); wDrawCloneBackground( mainD.d, bitmap_d.d ); wDrawShowBackground( bitmap_d.d, bitmapPosX, bitmapPosY, bitmapWidth, GetLayoutBackGroundAngle(), GetLayoutBackGroundScreen() ); } if( outputBitMapTogglesV & BITMAPDRAWCENTERLINE ) { bitmap_d.options |= DC_CENTERLINE; } else { bitmap_d.options &= ~DC_CENTERLINE; } DrawTracks( &bitmap_d, bitmap_d.scale, bitmap_d.orig, bitmap_d.size ); InfoMessage( _( "Writing bitmap to file" ) ); if( !wBitMapWriteFile( bitmap_d.d, fileName[0] ) ) { NoticeMessage( MSG_WBITMAP_FAILED, _( "Ok" ), NULL ); result = false; } else { InfoMessage( "" ); result = true; } wSetCursor( mainD.d, defaultCursor ); wBitMapDelete( bitmap_d.d ); return result; } /******************************************************************************* * * Output BitMap Dialog * */ static wWin_p outputBitMapW; static char *bitmapTogglesLabels[] = { N_( "Layout Titles" ), N_( "Borders" ), N_( "Centerline of Track" ), N_( "Background Image" ), NULL }; static paramFloatRange_t dpiRange = { 0.1, 100.0, 60 }; static paramData_t outputBitMapPLs[] = { #define I_TOGGLES (0) { PD_TOGGLE, &outputBitMapTogglesV, "toggles", PDO_NOPSHUPD, bitmapTogglesLabels, N_( "Include " ) }, #define I_DENSITY (1) { PD_FLOAT, &outputBitMapDensity, "density", PDO_NOPSHUPD, &dpiRange, N_( "Resolution " ) }, { PD_MESSAGE, N_( "dpi" ), NULL, PDO_DLGHORZ }, { PD_MESSAGE, N_( "Bitmap Size " ), NULL, PDO_NOPSHUPD | PDO_DLGRESETMARGIN, 0 }, #define I_MSG1 (4) { PD_MESSAGE, N_( "99999 by 99999 pixels" ), NULL, PDO_DLGHORZ | PDO_DLGUNDERCMDBUTT /* | PDO_DLGWIDE */, I2VP( 180 )}, { PD_MESSAGE, N_( "Approximate File Size " ), NULL, PDO_NOPSHUPD, 0 }, #define I_MSG2 (6) { PD_MESSAGE, N_( "999.9Mb" ), NULL, PDO_DLGHORZ | PDO_DLGUNDERCMDBUTT | PDO_DLGBOXEND, I2VP( 180 ) }, }; static paramGroup_t outputBitMapPG = { "outputbitmap", 0, outputBitMapPLs, COUNT( outputBitMapPLs ) }; /** * The upper limit for the dpi setting is calculated. The limit is set * to make sute that the number of pixels in any direction is below the * maximum value. * * \param [in] size The size of the layout * \param [in] marginX The total size of margins in X direction * \param [in] marginY The total size of margins in Y direction * * \returns the maximum allowed dpi value */ static double CalculateMaxDPI( coOrd size, POS_T marginX, POS_T marginY ) { POS_T maxSize; POS_T maxMargin; double maxDpi; if( size.x > size.y ) { maxMargin = marginX; maxSize = size.x; } else { maxMargin = marginY; maxSize = size.y; } maxDpi = ( BITMAPDIM - maxMargin * mainD.dpi ) / maxSize; return( floor( maxDpi ) ); } /** * Display the pixel size of the bitmap */ static void OutputBitmapPixelSize( void ) { DynString message; DynStringMalloc( &message, 16 ); ParamLoadData( &outputBitMapPG ); DynStringPrintf( &message, _( "%ld by %ld pixels" ), bitmap_w, bitmap_h ); ParamLoadMessage( &outputBitMapPG, I_MSG1, DynStringToCStr( &message ) ); DynStringFree( &message ); } /** * Display and return the file size of the bitmap * * \returns the estimated file size */ static FLOAT_T OutputBitmapFileSize( void ) { DynString message; DynStringMalloc( &message, 16 ); ParamLoadData( &outputBitMapPG ); FLOAT_T size; size = ( FLOAT_T )bitmap_w * bitmap_h; if( size < 1e4 ) { DynStringPrintf( &message, _( "%0.0f" ), size ); } else if( size < 1e6 ) { DynStringPrintf( &message, _( "%0.1fKb" ), ( size + 50.0 ) / 1e3 ); } else if( size < 1e9 ) { DynStringPrintf( &message, _( "%0.1fMb" ), ( size + 5e4 ) / 1e6 ); } else { DynStringPrintf( &message, _( "%0.1fGb" ), ( size + 5e7 ) / 1e9 ); } ParamLoadMessage( &outputBitMapPG, I_MSG2, DynStringToCStr( &message ) ); DynStringFree( &message ); return( size ); } /** * Compute pixel size of bitmap */ static void ComputeBitmapSize( void ) { FLOAT_T Lborder=0.0, Rborder=0.0, Tborder=0.0, Bborder=0.0; bitmap_d.dpi = mainD.dpi; bitmap_d.scale = mainD.dpi / outputBitMapDensity; if( outputBitMapTogglesV & ( BITMAPDRAWFRAMEONLY | BITMAPDRAWTITLE ) ) { Lborder = LEFTMARGIN; Rborder = DEFAULTMARGIN; Tborder = DEFAULTMARGIN; Bborder = BOTTOMMARGIN; } if( outputBitMapTogglesV & BITMAPDRAWTITLE ) { Tborder += 2 * LINEHEIGHT; Bborder += LINEHEIGHT; } dpiRange.high = CalculateMaxDPI( mapD.size, Lborder + Rborder, Bborder + Tborder ); bitmap_d.orig.x = -Lborder*bitmap_d.scale; bitmap_d.size.x = mapD.size.x + ( Lborder + Rborder )*bitmap_d.scale; bitmap_d.orig.y = -Bborder*bitmap_d.scale; bitmap_d.size.y = mapD.size.y + ( Bborder + Tborder )*bitmap_d.scale; bitmap_w = ( wWinPix_t )( bitmap_d.size.x / bitmap_d.scale*bitmap_d.dpi ); bitmap_h = ( wWinPix_t )( bitmap_d.size.y / bitmap_d.scale*bitmap_d.dpi ); } /** * Update the dialog for changed bitmap settings. Based on the new dimensions * selected options the bitmap size and and maximum density is calculated and * displayed. */ void UpdateBitmapDialog( void ) { ParamLoadData( &outputBitMapPG ); ComputeBitmapSize(); ParamLoadControl( &outputBitMapPG, I_DENSITY ); // trigger range check if( outputBitMapDensity > dpiRange.high ) { ParamDialogOkActive( &outputBitMapPG, false ); } else { ParamDialogOkActive( &outputBitMapPG, true ); } OutputBitmapPixelSize(); OutputBitmapFileSize(); } /** * Check input from bitmap options dialog and trigger file name selection * * \param [in,out] junk If non-null, the junk. */ static void OutputBitMapOk( void * unused ) { FLOAT_T size; size = OutputBitmapFileSize(); if( size > BITMAPSIZE ) { if( NoticeMessage( MSG_BITMAP_SIZE_WARNING, _( "Continue" ), _( "Cancel" ) )==0 ) { return; } } wHide( outputBitMapW ); if( !bitmap_fs ) { bitmap_fs = wFilSelCreate( mainW, FS_SAVE, 0, _( "Save Bitmap" ), _( "Portable Network Graphics format (*.png)|*.png|" \ "JPEG format (*.jpg)|*.jpg" ), SaveBitmapFile, NULL ); } wFilSelect( bitmap_fs, GetCurrentPath( BITMAPPATHKEY ) ); } /** * Handle changes for bitmap export. Only changes relevant here are * changes to the map. * * \param changes The changes. */ static void OutputBitMapChange( long changes ) { if( ( changes & CHANGE_MAP ) && outputBitMapW ) { ParamLoadControls( &outputBitMapPG ); ComputeBitmapSize(); } return; } /** * Executes the output bit map operation * * \param [in,out] unused. */ static void DoOutputBitMap( void* unused ) { if( outputBitMapW == NULL ) { outputBitMapW = ParamCreateDialog( &outputBitMapPG, MakeWindowTitle( _( "Export to bitmap" ) ), _( "Ok" ), OutputBitMapOk, wHide, TRUE, NULL, 0, ( paramChangeProc )UpdateBitmapDialog ); } ParamLoadControls( &outputBitMapPG ); ParamGroupRecord( &outputBitMapPG ); UpdateBitmapDialog(); if( dpiRange.high < outputBitMapDensity ) { outputBitMapDensity = dpiRange.high; ParamLoadControl( &outputBitMapPG, I_DENSITY ); } wShow( outputBitMapW ); } /** * Initialize bitmap output * * \returns entry point for bitmap export */ EXPORT addButtonCallBack_t OutputBitMapInit( void ) { ParamRegister( &outputBitMapPG ); RegisterChangeNotification( OutputBitMapChange ); return &DoOutputBitMap; }