PDF plotting: support bitmaps with transparency.
Fixes https://gitlab.com/kicad/code/kicad/-/issues/5979
This commit is contained in:
parent
30c2049977
commit
2689037bde
|
@ -33,6 +33,8 @@
|
|||
#include <wx/filename.h>
|
||||
#include <wx/mstream.h>
|
||||
#include <wx/zstream.h>
|
||||
#include <wx/wfstream.h>
|
||||
#include <wx/datstrm.h>
|
||||
|
||||
#include <advanced_config.h>
|
||||
#include <eda_text.h> // for IsGotoPageHref
|
||||
|
@ -471,6 +473,53 @@ void PDF_PLOTTER::PlotImage( const wxImage& aImage, const VECTOR2I& aPos, double
|
|||
VECTOR2I start( aPos.x - drawsize.x / 2, aPos.y + drawsize.y / 2 );
|
||||
VECTOR2D dev_start = userToDeviceCoordinates( start );
|
||||
|
||||
// Deduplicate images
|
||||
auto findHandleForImage = [&]( const wxImage& aImage ) -> int
|
||||
{
|
||||
for( const auto& [imgHandle, image] : m_imageHandles )
|
||||
{
|
||||
if( image.IsSameAs( aImage ) )
|
||||
return imgHandle;
|
||||
|
||||
if( image.GetWidth() != aImage.GetWidth() )
|
||||
continue;
|
||||
|
||||
if( image.GetHeight() != aImage.GetHeight() )
|
||||
continue;
|
||||
|
||||
if( image.GetType() != aImage.GetType() )
|
||||
continue;
|
||||
|
||||
if( image.HasAlpha() != aImage.HasAlpha() )
|
||||
continue;
|
||||
|
||||
if( image.HasMask() != aImage.HasMask() || image.GetMaskRed() != aImage.GetMaskRed()
|
||||
|| image.GetMaskGreen() != aImage.GetMaskGreen()
|
||||
|| image.GetMaskBlue() != aImage.GetMaskBlue() )
|
||||
continue;
|
||||
|
||||
int pixCount = image.GetWidth() * image.GetHeight();
|
||||
|
||||
if( memcmp( image.GetData(), aImage.GetData(), pixCount * 3 ) != 0 )
|
||||
continue;
|
||||
|
||||
if( image.HasAlpha() && memcmp( image.GetAlpha(), aImage.GetAlpha(), pixCount ) != 0 )
|
||||
continue;
|
||||
|
||||
return imgHandle;
|
||||
}
|
||||
|
||||
return -1;
|
||||
};
|
||||
|
||||
int imgHandle = findHandleForImage( aImage );
|
||||
|
||||
if( imgHandle == -1 )
|
||||
{
|
||||
imgHandle = allocPdfObject();
|
||||
m_imageHandles.emplace( imgHandle, aImage );
|
||||
}
|
||||
|
||||
/* PDF has an uhm... simplified coordinate system handling. There is
|
||||
*one* operator to do everything (the PS concat equivalent). At least
|
||||
they kept the matrix stack to save restore environments. Also images
|
||||
|
@ -486,75 +535,8 @@ void PDF_PLOTTER::PlotImage( const wxImage& aImage, const VECTOR2I& aPos, double
|
|||
userToDeviceSize( drawsize.y ),
|
||||
dev_start.x, dev_start.y );
|
||||
|
||||
/* An inline image is a cross between a dictionary and a stream.
|
||||
A real ugly construct (compared with the elegance of the PDF
|
||||
format). Also it accepts some 'abbreviations', which is stupid
|
||||
since the content stream is usually compressed anyway... */
|
||||
fprintf( m_workFile,
|
||||
"BI\n"
|
||||
" /BPC 8\n"
|
||||
" /CS %s\n"
|
||||
" /W %d\n"
|
||||
" /H %d\n"
|
||||
"ID\n", m_colorMode ? "/RGB" : "/G", pix_size.x, pix_size.y );
|
||||
|
||||
wxColor bg = m_renderSettings->GetBackgroundColor() != COLOR4D::UNSPECIFIED
|
||||
? m_renderSettings->GetBackgroundColor().ToColour()
|
||||
: wxColor( 255, 255, 255 );
|
||||
|
||||
/* Here comes the stream (in binary!). I *could* have hex or ascii84
|
||||
encoded it, but who cares? I'll go through zlib anyway */
|
||||
for( int y = 0; y < pix_size.y; y++ )
|
||||
{
|
||||
for( int x = 0; x < pix_size.x; x++ )
|
||||
{
|
||||
unsigned char r = aImage.GetRed( x, y ) & 0xFF;
|
||||
unsigned char g = aImage.GetGreen( x, y ) & 0xFF;
|
||||
unsigned char b = aImage.GetBlue( x, y ) & 0xFF;
|
||||
|
||||
// PDF inline images don't support alpha, so blend with background color
|
||||
if( aImage.HasAlpha() )
|
||||
{
|
||||
unsigned char alpha = aImage.GetAlpha( x, y ) & 0xFF;
|
||||
|
||||
if( alpha < 0xFF )
|
||||
{
|
||||
float d = alpha / 255.0;
|
||||
|
||||
r = wxColour::AlphaBlend( r, bg.Red(), d );
|
||||
g = wxColour::AlphaBlend( g, bg.Green(), d );
|
||||
b = wxColour::AlphaBlend( b, bg.Blue(), d );
|
||||
}
|
||||
}
|
||||
|
||||
if( aImage.HasMask() )
|
||||
{
|
||||
if( r == aImage.GetMaskRed() && g == aImage.GetMaskGreen()
|
||||
&& b == aImage.GetMaskBlue() )
|
||||
{
|
||||
r = 0xFF;
|
||||
g = 0xFF;
|
||||
b = 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
// As usual these days, stdio buffering has to suffeeeeerrrr
|
||||
if( m_colorMode )
|
||||
{
|
||||
putc( r, m_workFile );
|
||||
putc( g, m_workFile );
|
||||
putc( b, m_workFile );
|
||||
}
|
||||
else
|
||||
{
|
||||
// Greyscale conversion (CIE 1931)
|
||||
unsigned char grey = KiROUND( r * 0.2126 + g * 0.7152 + b * 0.0722 );
|
||||
putc( grey, m_workFile );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fputs( "EI Q\n", m_workFile ); // Finish step 2 and do step 3
|
||||
fprintf( m_workFile, "/Im%d Do\n", imgHandle );
|
||||
fputs( "Q\n", m_workFile );
|
||||
}
|
||||
|
||||
|
||||
|
@ -712,6 +694,82 @@ void PDF_PLOTTER::StartPage( const wxString& aPageNumber, const wxString& aPageN
|
|||
}
|
||||
|
||||
|
||||
void WriteImageStream( const wxImage& aImage, wxDataOutputStream& aOut, wxColor bg, bool colorMode )
|
||||
{
|
||||
int w = aImage.GetWidth();
|
||||
int h = aImage.GetHeight();
|
||||
|
||||
for( int y = 0; y < h; y++ )
|
||||
{
|
||||
for( int x = 0; x < w; x++ )
|
||||
{
|
||||
unsigned char r = aImage.GetRed( x, y ) & 0xFF;
|
||||
unsigned char g = aImage.GetGreen( x, y ) & 0xFF;
|
||||
unsigned char b = aImage.GetBlue( x, y ) & 0xFF;
|
||||
|
||||
if( aImage.HasMask() )
|
||||
{
|
||||
if( r == aImage.GetMaskRed() && g == aImage.GetMaskGreen()
|
||||
&& b == aImage.GetMaskBlue() )
|
||||
{
|
||||
r = bg.Red();
|
||||
g = bg.Green();
|
||||
b = bg.Blue();
|
||||
}
|
||||
}
|
||||
|
||||
if( colorMode )
|
||||
{
|
||||
aOut.Write8( r );
|
||||
aOut.Write8( g );
|
||||
aOut.Write8( b );
|
||||
}
|
||||
else
|
||||
{
|
||||
// Greyscale conversion (CIE 1931)
|
||||
unsigned char grey = KiROUND( r * 0.2126 + g * 0.7152 + b * 0.0722 );
|
||||
|
||||
aOut.Write8( grey );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void WriteImageSMaskStream( const wxImage& aImage, wxDataOutputStream& aOut )
|
||||
{
|
||||
int w = aImage.GetWidth();
|
||||
int h = aImage.GetHeight();
|
||||
|
||||
if( aImage.HasMask() )
|
||||
{
|
||||
for( int y = 0; y < h; y++ )
|
||||
{
|
||||
for( int x = 0; x < w; x++ )
|
||||
{
|
||||
unsigned char a = 255;
|
||||
unsigned char r = aImage.GetRed( x, y );
|
||||
unsigned char g = aImage.GetGreen( x, y );
|
||||
unsigned char b = aImage.GetBlue( x, y );
|
||||
|
||||
if( r == aImage.GetMaskRed() && g == aImage.GetMaskGreen()
|
||||
&& b == aImage.GetMaskBlue() )
|
||||
{
|
||||
a = 0;
|
||||
}
|
||||
|
||||
aOut.Write8( a );
|
||||
}
|
||||
}
|
||||
}
|
||||
else if( aImage.HasAlpha() )
|
||||
{
|
||||
int size = w * h;
|
||||
aOut.Write8( aImage.GetAlpha(), size );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void PDF_PLOTTER::ClosePage()
|
||||
{
|
||||
wxASSERT( m_workFile );
|
||||
|
@ -807,11 +865,13 @@ void PDF_PLOTTER::ClosePage()
|
|||
"/Parent %d 0 R\n"
|
||||
"/Resources <<\n"
|
||||
" /ProcSet [/PDF /Text /ImageC /ImageB]\n"
|
||||
" /Font %d 0 R >>\n"
|
||||
" /Font %d 0 R\n"
|
||||
" /XObject %d 0 R >>\n"
|
||||
"/MediaBox [0 0 %g %g]\n"
|
||||
"/Contents %d 0 R\n",
|
||||
m_pageTreeHandle,
|
||||
m_fontResDictHandle,
|
||||
m_imgResDictHandle,
|
||||
psPaperSize.x,
|
||||
psPaperSize.y,
|
||||
m_pageStreamHandle );
|
||||
|
@ -909,6 +969,8 @@ bool PDF_PLOTTER::StartPlot( const wxString& aPageNumber, const wxString& aPageN
|
|||
(it *could* be inherited via the Pages tree */
|
||||
m_fontResDictHandle = allocPdfObject();
|
||||
|
||||
m_imgResDictHandle = allocPdfObject();
|
||||
|
||||
m_jsNamesHandle = allocPdfObject();
|
||||
|
||||
/* Now, the PDF is read from the end, (more or less)... so we start
|
||||
|
@ -1109,6 +1171,112 @@ bool PDF_PLOTTER::EndPlot()
|
|||
fputs( ">>\n", m_outputFile );
|
||||
closePdfObject();
|
||||
|
||||
// Named image dictionary (was allocated, now we emit it)
|
||||
startPdfObject( m_imgResDictHandle );
|
||||
fputs( "<<\n", m_outputFile );
|
||||
|
||||
for( const auto& [imgHandle, image] : m_imageHandles )
|
||||
{
|
||||
fprintf( m_outputFile, " /Im%d %d 0 R\n", imgHandle, imgHandle );
|
||||
}
|
||||
|
||||
fputs( ">>\n", m_outputFile );
|
||||
closePdfObject();
|
||||
|
||||
// Emit images with optional SMask for transparency
|
||||
for( const auto& [imgHandle, image] : m_imageHandles )
|
||||
{
|
||||
// Init wxFFile so wxFFileOutputStream won't close file in dtor.
|
||||
wxFFile outputFFile( m_outputFile );
|
||||
|
||||
// Image
|
||||
startPdfObject( imgHandle );
|
||||
int imgLenHandle = allocPdfObject();
|
||||
int smaskHandle = ( image.HasAlpha() || image.HasMask() ) ? allocPdfObject() : -1;
|
||||
|
||||
fprintf( m_outputFile,
|
||||
"<<\n"
|
||||
"/Type /XObject\n"
|
||||
"/Subtype /Image\n"
|
||||
"/BitsPerComponent 8\n"
|
||||
"/ColorSpace %s\n"
|
||||
"/Width %d\n"
|
||||
"/Height %d\n"
|
||||
"/Filter /FlateDecode\n"
|
||||
"/Length %d 0 R\n", // Length is deferred
|
||||
m_colorMode ? "/DeviceRGB" : "/DeviceGray", image.GetWidth(), image.GetHeight(),
|
||||
imgLenHandle );
|
||||
|
||||
if( smaskHandle != -1 )
|
||||
fprintf( m_outputFile, "/SMask %d 0 R\n", smaskHandle );
|
||||
|
||||
fputs( ">>\n", m_outputFile );
|
||||
fputs( "stream\n", m_outputFile );
|
||||
|
||||
long imgStreamStart = ftell( m_outputFile );
|
||||
|
||||
{
|
||||
wxFFileOutputStream ffos( outputFFile );
|
||||
wxZlibOutputStream zos( ffos, wxZ_BEST_COMPRESSION, wxZLIB_ZLIB );
|
||||
wxDataOutputStream dos( zos );
|
||||
|
||||
WriteImageStream( image, dos, m_renderSettings->GetBackgroundColor().ToColour(),
|
||||
m_colorMode );
|
||||
}
|
||||
|
||||
long imgStreamSize = ftell( m_outputFile ) - imgStreamStart;
|
||||
|
||||
fputs( "\nendstream\n", m_outputFile );
|
||||
closePdfObject();
|
||||
|
||||
startPdfObject( imgLenHandle );
|
||||
fprintf( m_outputFile, "%ld\n", imgStreamSize );
|
||||
closePdfObject();
|
||||
|
||||
if( smaskHandle != -1 )
|
||||
{
|
||||
// SMask
|
||||
startPdfObject( smaskHandle );
|
||||
int smaskLenHandle = allocPdfObject();
|
||||
|
||||
fprintf( m_outputFile,
|
||||
"<<\n"
|
||||
"/Type /XObject\n"
|
||||
"/Subtype /Image\n"
|
||||
"/BitsPerComponent 8\n"
|
||||
"/ColorSpace /DeviceGray\n"
|
||||
"/Width %d\n"
|
||||
"/Height %d\n"
|
||||
"/Length %d 0 R\n"
|
||||
"/Filter /FlateDecode\n"
|
||||
">>\n", // Length is deferred
|
||||
image.GetWidth(), image.GetHeight(), smaskLenHandle );
|
||||
|
||||
fputs( "stream\n", m_outputFile );
|
||||
|
||||
long smaskStreamStart = ftell( m_outputFile );
|
||||
|
||||
{
|
||||
wxFFileOutputStream ffos( outputFFile );
|
||||
wxZlibOutputStream zos( ffos, wxZ_BEST_COMPRESSION, wxZLIB_ZLIB );
|
||||
wxDataOutputStream dos( zos );
|
||||
|
||||
WriteImageSMaskStream( image, dos );
|
||||
}
|
||||
|
||||
long smaskStreamSize = ftell( m_outputFile ) - smaskStreamStart;
|
||||
|
||||
fputs( "\nendstream\n", m_outputFile );
|
||||
closePdfObject();
|
||||
|
||||
startPdfObject( smaskLenHandle );
|
||||
fprintf( m_outputFile, "%u\n", (unsigned) smaskStreamSize );
|
||||
closePdfObject();
|
||||
}
|
||||
|
||||
outputFFile.Detach(); // Don't close it
|
||||
}
|
||||
|
||||
for( const auto& [ linkHandle, linkPair ] : m_hyperlinkHandles )
|
||||
{
|
||||
const BOX2D& box = linkPair.first;
|
||||
|
|
|
@ -85,9 +85,28 @@ void PlotDrawingSheet( PLOTTER* plotter, const PROJECT* aProject, const TITLE_BL
|
|||
|
||||
drawList.BuildDrawItemsList( aPageInfo, aTitleBlock );
|
||||
|
||||
// Draw item list
|
||||
// Draw bitmaps first
|
||||
for( DS_DRAW_ITEM_BASE* item = drawList.GetFirst(); item; item = drawList.GetNext() )
|
||||
{
|
||||
if( item->Type() == WSG_BITMAP_T )
|
||||
{
|
||||
DS_DRAW_ITEM_BITMAP* drawItem = (DS_DRAW_ITEM_BITMAP*) item;
|
||||
DS_DATA_ITEM_BITMAP* bitmap = (DS_DATA_ITEM_BITMAP*) drawItem->GetPeer();
|
||||
|
||||
if( bitmap->m_ImageBitmap == nullptr )
|
||||
continue;
|
||||
|
||||
bitmap->m_ImageBitmap->PlotImage( plotter, drawItem->GetPosition(), plotColor,
|
||||
PLOTTER::USE_DEFAULT_LINE_WIDTH );
|
||||
}
|
||||
}
|
||||
|
||||
// Draw other items
|
||||
for( DS_DRAW_ITEM_BASE* item = drawList.GetFirst(); item; item = drawList.GetNext() )
|
||||
{
|
||||
if( item->Type() == WSG_BITMAP_T )
|
||||
continue;
|
||||
|
||||
plotter->SetColor( plotColor );
|
||||
plotter->SetCurrentLineWidth( PLOTTER::USE_DEFAULT_LINE_WIDTH );
|
||||
|
||||
|
@ -157,19 +176,6 @@ void PlotDrawingSheet( PLOTTER* plotter, const PROJECT* aProject, const TITLE_BL
|
|||
}
|
||||
break;
|
||||
|
||||
case WSG_BITMAP_T:
|
||||
{
|
||||
DS_DRAW_ITEM_BITMAP* drawItem = (DS_DRAW_ITEM_BITMAP*) item;
|
||||
DS_DATA_ITEM_BITMAP* bitmap = (DS_DATA_ITEM_BITMAP*) drawItem->GetPeer();
|
||||
|
||||
if( bitmap->m_ImageBitmap == nullptr )
|
||||
break;
|
||||
|
||||
bitmap->m_ImageBitmap->PlotImage( plotter, drawItem->GetPosition(), plotColor,
|
||||
PLOTTER::USE_DEFAULT_LINE_WIDTH );
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
wxFAIL_MSG( "PlotDrawingSheet(): Unknown drawing sheet item." );
|
||||
break;
|
||||
|
|
|
@ -493,6 +493,7 @@ protected:
|
|||
|
||||
int m_pageTreeHandle; ///< Handle to the root of the page tree object
|
||||
int m_fontResDictHandle; ///< Font resource dictionary
|
||||
int m_imgResDictHandle; ///< Image resource dictionary
|
||||
int m_jsNamesHandle; ///< Handle for Names dictionary with JS
|
||||
std::vector<int> m_pageHandles; ///< Handles to the page objects
|
||||
int m_pageStreamHandle; ///< Handle of the page content object
|
||||
|
@ -515,6 +516,8 @@ protected:
|
|||
|
||||
std::map<wxString, std::vector<std::pair<BOX2I, wxString>>> m_bookmarksInPage;
|
||||
|
||||
std::map<int, wxImage> m_imageHandles;
|
||||
|
||||
std::unique_ptr<OUTLINE_NODE> m_outlineRoot; ///< Root outline node
|
||||
int m_totalOutlineNodes; ///< Total number of outline nodes
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue