#include <iostream>
#include <sstream>
#include <string>
#include <algorithm>
#include <iterator>
#include <openbabel/babelconfig.h>
#include <openbabel/obconversion.h>
#include <openbabel/mol.h>
#include <zlib.h>
using namespace std;
namespace OpenBabel
{
class PNGFormat : public OBFormat
{
public:
PNGFormat()
{
OBConversion::RegisterFormat("png",this);
OBConversion::RegisterOptionParam("y", this, 1, OBConversion::INOPTIONS);
OBConversion::RegisterOptionParam("y", this, 1, OBConversion::OUTOPTIONS);
}
virtual const char* Description()
{
return
"PNG 2D depiction\n"
"or add/extract chemical structures from a .png file\n\n"
"The PNG format has several uses. The most common is to generate a\n"
":file:`.png` file for one or more molecules.\n"
"2D coordinates are generated if not present::\n\n"
" obabel mymol.smi -O image.png\n\n"
"Chemical structure data can be embedded in the :file:`.png` file\n"
"(in a ``tEXt`` chunk)::\n\n"
" obabel mymol.mol -O image.png -xO molfile\n\n"
"The parameter of the ``-xO`` option specifies the format (\"file\"can be added).\n"
"Note that if you intend to embed a 2D or 3D format, you may have to call\n"
"``--gen2d`` or ``--gen3d`` to generate the required coordinates if they are\n"
"not present in the input.\n\n"
"Molecules can also be embedded in an existing PNG file::\n\n"
" obabel existing.png mymol1.smi mymol2.mol -O augmented.png -xO mol\n\n"
"Reading from a PNG file will extract any embedded chemical structure data::\n\n"
" obabel augmented.png -O contents.sdf\n\n"
"Write Options e.g. -xp 500\n"
" p <pixels> image size, default 300\n"
" w <pixels> image width (or from image size)\n"
" h <pixels> image height (or from image size)\n"
" c# number of columns in table\n"
" r# number of rows in table\n"
" N# max number objects to be output\n"
" u no element-specific atom coloring\n"
" Use this option to produce a black and white diagram\n"
" U do not use internally-specified color\n"
" e.g. atom color read from cml or generated by internal code\n"
" b <color> background color, default white\n"
" e.g ``-xb yellow`` or ``-xb #88ff00`` ``-xb none`` is transparent.\n"
" Just ``-xb`` is black with white bonds.\n"
" The atom symbol colors work with black and white backgrounds,\n"
" but may not with other colors.\n"
" B <color> bond color, default black\n"
" e.g ``-xB`` yellow or ``-xB #88ff00``\n"
" C do not draw terminal C (and attached H) explicitly\n"
" The default is to draw all hetero atoms and terminal C explicitly,\n"
" together with their attched hydrogens.\n"
" a draw all carbon atoms\n"
" So propane would display as H3C-CH2-CH3\n"
" d do not display molecule name\n"
" m do not add margins to the image\n"
" This only applies if there is a single molecule to depict.\n"
" Implies -xd.\n"
" s use asymmetric double bonds\n"
" t use thicker lines\n"
" A display aliases, if present\n"
" This applies to structures which have an alternative, usually\n"
" shorter, representation already present. This might have been input\n"
" from an A or S superatom entry in an sd or mol file, or can be\n"
" generated using the --genalias option. For example::\n \n"
" obabel -:\"c1cc(C=O)ccc1C(=O)O\" -O out.png\n"
" --genalias -xA\n \n"
" would add a aliases COOH and CHO to represent the carboxyl and\n"
" aldehyde groups and would display them as such in the svg diagram.\n"
" The aliases which are recognized are in data/superatom.txt, which\n"
" can be edited.\n"
" O <format ID> Format of embedded text\n"
" For example, ``molfile`` or ``smi``.\n"
" If there is no parameter, input format is used.\n"
" y <additional chunk ID> Write to a chunk with specified ID\n\n"
"Read Options e.g. -ay\n"
" y <additional chunk ID> Look also in chunks with specified ID\n\n"
"If Cairo was not found when Open Babel was compiled, then\n"
"the 2D depiction will be unavailable. However, it will still be\n"
"possible to extract and embed chemical data in :file:`.png` files.\n"
"\n"
".. seealso::\n\n"
" :ref:`PNG_2D_depiction`\n\n"
;
};
virtual const char* TargetClassDescription()
{
static string txt;
txt = " PNG_files\n"; txt += OBFormat::TargetClassDescription(); return txt.c_str();
}
virtual unsigned int Flags()
{
return READONEONLY | READBINARY | WRITEBINARY | DEPICTION2D;
};
virtual bool ReadChemObject(OBConversion* pConv)
{
bool ret = ReadMolecule(nullptr, pConv);
pConv->GetChemObject(); return ret;
};
virtual bool WriteChemObject(OBConversion* pConv)
{
if(!CopyOfInput.empty() && bytesToIEND>0)
{
OBBase* pOb = pConv->GetChemObject();
return WriteMolecule(pOb, pConv);
}
else
{
_hasInputPngFile = false;
OBFormat* ppng2 = OBConversion::FindFormat("_png2");
if(!ppng2)
{
obErrorLog.ThrowError("PNG Format","PNG2Format not found. Probably the Cairo library is not loaded.", obError);
return false;
}
bool ret = ppng2->WriteChemObject(pConv);
if(pConv->IsLast())
pConv->SetOutFormat(""); return ret;
}
};
virtual bool ReadMolecule(OBBase* pOb, OBConversion* pConv);
virtual bool WriteMolecule(OBBase* pOb, OBConversion* pConv);
private:
int _count; vector<char> CopyOfInput;
unsigned bytesToIEND; unsigned origBytesToIEND; bool _hasInputPngFile;
unsigned long Read32(istream& ifs)
{
char ch;
unsigned long val=0;
for(int i=0; i<4; ++i)
{
if(!ifs.get(ch))
return 0;
val = val * 0x100 + (unsigned char)ch;
}
return val;
}
void Write32(unsigned long val, ostream& ofs)
{
char p[4];
for(int i=0; i<4; ++i)
{
p[3-i] = (char)val % 0x100;
val /= 0x100;
}
ofs.write(p, 4);
}
};
PNGFormat thePNGFormat;
bool PNGFormat::ReadMolecule(OBBase* pOb, OBConversion* pConv)
{
istream& ifs = *pConv->GetInStream();
if(pConv->IsFirstInput())
{
_count=0;
_hasInputPngFile=true;
}
const unsigned char pngheader[] = {137,80,78,71,13,10,26,10,0};
char readbytes[9];
ifs.read(readbytes, 8);
if(!equal(pngheader, pngheader+8, readbytes))
{
obErrorLog.ThrowError("PNG Format","Not a PNG file", obError);
return false;
}
while(ifs)
{
unsigned int len = Read32(ifs);
ifs.read(readbytes,4);
string chunkid(readbytes, readbytes+4);
if(chunkid=="IEND")
{
bytesToIEND = ifs.tellg();
bytesToIEND -= 8;
break;
}
streampos pos = ifs.tellg();
const char* altid = pConv->IsOption("y",OBConversion::INOPTIONS);
if(chunkid=="tEXt" || chunkid=="zTXt" || (altid && chunkid==altid))
{
string keyword;
getline(ifs, keyword, '\0');
unsigned int datalength = len - keyword.size()-1;
transform(keyword.begin(),keyword.end(),keyword.begin(),::tolower);
string::size_type pos = keyword.find("file");
if(pos!=string::npos)
keyword.erase(pos);
OBFormat* pFormat = OBConversion::FindFormat(keyword.c_str());
if(pFormat)
{
stringstream ss;
if(chunkid[0]!='z')
{
istreambuf_iterator<char> initer(ifs);
ostreambuf_iterator<char> outiter(ss);
for (unsigned int i = 0; i < datalength; ++i)
*outiter++ = *initer++;
}
else
{
Bytef* pCompTxt = new Bytef[datalength];
ifs.read((char*)pCompTxt, datalength);
--datalength; uLongf uncompLen;
Bytef* pUncTxt = new Bytef[datalength*6]; if(*pCompTxt!=0
|| uncompress(pUncTxt, &uncompLen, pCompTxt+1, datalength)!=Z_OK)
{
obErrorLog.ThrowError("PNG Format","Errors in decompression", obError);
delete[] pUncTxt;
delete[] pCompTxt;
return false;
}
pUncTxt[uncompLen] = '\0';
ss.str((char*)pUncTxt);
delete[] pUncTxt;
delete[] pCompTxt;
}
OBConversion conv2(&ss, pConv->GetOutStream());
conv2.CopyOptions(pConv);
conv2.SetInAndOutFormats(pFormat, pConv->GetOutFormat());
_count += conv2.Convert();
ifs.ignore(4); continue; }
}
ifs.seekg(pos);
ifs.ignore(len+4); }
CopyOfInput.clear();
if(pConv->GetOutFormat()==this)
{
ifs.seekg(0);
copy(istreambuf_iterator<char>(ifs), istreambuf_iterator<char>(),back_inserter(CopyOfInput));
}
if(pConv->IsLastFile() && _count>0)
{
pConv->ReportNumberConverted(_count); pConv->SetOutFormat(this); }
return true;
}
bool PNGFormat::WriteMolecule(OBBase* pOb, OBConversion* pConv)
{
ostream& ofs = *pConv->GetOutStream();
if(!CopyOfInput.empty() && bytesToIEND>0)
{
ostreambuf_iterator<char> outiter(pConv->GetOutStream()->rdbuf());
copy(CopyOfInput.begin(), CopyOfInput.begin()+bytesToIEND, outiter);
origBytesToIEND = bytesToIEND;
bytesToIEND=0; }
const char* otxt = pConv->IsOption("O", OBConversion::OUTOPTIONS);
OBConversion conv2;
conv2.CopyOptions(pConv); string formatID;
if(otxt && *otxt)
{
formatID = otxt;
string::size_type pos = formatID.find("file");
if(pos!=string::npos)
formatID.erase(pos);
}
else formatID = pConv->GetInFormat()->GetID();
if(!conv2.SetOutFormat(OBConversion::FindFormat(formatID)))
{
obErrorLog.ThrowError("PNG Format","Format not found", obError);
return false;
}
stringstream ss;
ss.str("");
const char* pid = pConv->IsOption("y");
if(pid && strlen(pid)==4)
ss << pid;
else
ss << "tEXt";
ss << formatID << '\0';
bool ret = conv2.Write(pOb, &ss);
if(ret)
{
unsigned long len = ss.str().size() - 4; Write32(len, ofs);
ofs << ss.str();
uLong crc = crc32(0L, Z_NULL, 0);
crc = crc32(crc, (unsigned char*)ss.str().c_str(), ss.str().size());
Write32(crc, ofs);
}
else
obErrorLog.ThrowError("PNG Format","Failed when converting the molecule", obError);
if(pConv->IsLast())
{
ostreambuf_iterator<char> outiter(pConv->GetOutStream()->rdbuf());
copy(CopyOfInput.begin()+origBytesToIEND, CopyOfInput.end(), outiter);
CopyOfInput.clear();
if(_hasInputPngFile)
pConv->SetOutputIndex(pConv->GetOutputIndex()-1);
pConv->SetOutFormat(formatID.c_str());
}
return ret;
}
}