1 /**********************************************************************************************
2     Copyright (C) 2018 Oliver Eichler <oliver.eichler@gmx.de>
3 
4     This program is free software: you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation, either version 3 of the License, or
7     (at your option) any later version.
8 
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13 
14     You should have received a copy of the GNU General Public License
15     along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 
17 **********************************************************************************************/
18 
19 #include "CApp.h"
20 
21 #include <gdal_alg.h>
22 #include <gdal_priv.h>
23 #include <iostream>
24 
25 const GDALColorEntry CApp::noColor = {255, 255, 255, 0};
26 
printStdoutQString(const QString & str)27 void printStdoutQString(const QString& str)
28 {
29     QByteArray array = str.toUtf8();
30     printf("%s", array.data());
31     printf("\n");
32 }
33 
printStderrQString(const QString & str)34 void printStderrQString(const QString& str)
35 {
36     QByteArray array = str.toUtf8();
37     fprintf(stderr, "%s", array.data());
38     fprintf(stderr, "\n");
39 }
40 
41 
42 
CApp(qint32 ncolors,const QString & pctFilename,const QString & sctFilename,const QString & srcFilename,const QString & tarFilename)43 CApp::CApp(qint32 ncolors, const QString& pctFilename, const QString& sctFilename, const QString& srcFilename, const QString& tarFilename)
44     : ncolors(ncolors)
45     , pctFilename(pctFilename)
46     , sctFilename(sctFilename)
47     , srcFilename(srcFilename)
48     , tarFilename(tarFilename)
49 {
50     GDALAllRegister();
51 }
52 
exec()53 qint32 CApp::exec()
54 {
55     qint32 res = 0;
56     GDALColorTable* ct = nullptr;
57     GDALDataset* dsSrc = nullptr;
58     try
59     {
60         dsSrc = (GDALDataset*)GDALOpenShared(srcFilename.toUtf8(), GA_ReadOnly);
61         if(dsSrc == nullptr)
62         {
63             throw tr("Failed to open source file.");
64         }
65 
66         if(dsSrc->GetRasterCount() < 3 || dsSrc->GetRasterCount() > 4)
67         {
68             throw tr("Raster band count of source file must be either 3 or 4.");
69         }
70 
71         if(QFile(tarFilename).exists())
72         {
73             QFile::remove(tarFilename);
74         }
75 
76         ct = createColorTable(ncolors, pctFilename, dsSrc);
77         saveColorTable(ct, sctFilename);
78         ditherMap(dsSrc, tarFilename, ct);
79     }
80     catch(const QString& msg)
81     {
82         printStderrQString(msg);
83         res = -1;
84     }
85 
86 
87     GDALClose(dsSrc);
88     delete ct;
89     return res;
90 }
91 
createColorTable(qint32 ncolors,const QString & pctFilename,GDALDataset * dataset)92 GDALColorTable* CApp::createColorTable(qint32 ncolors, const QString& pctFilename, GDALDataset* dataset)
93 {
94     GDALColorTable* ct = nullptr;
95     try
96     {
97         if(pctFilename.isEmpty())
98         {
99             ct = (GDALColorTable*)GDALCreateColorTable(GPI_RGB);
100 
101             printStdoutQString(tr("Calculate optimal color table from source file"));
102 
103             int ok = GDALComputeMedianCutPCT(dataset->GetRasterBand(1),
104                                              dataset->GetRasterBand(2),
105                                              dataset->GetRasterBand(3),
106                                              nullptr,
107                                              ncolors,
108                                              ct,
109                                              GDALTermProgress,
110                                              0
111                                              );
112 
113             if(ok != CE_None)
114             {
115                 throw tr("Failed to create color table.");
116             }
117         }
118         else
119         {
120             GDALDataset* dsPct = (GDALDataset*)GDALOpenShared(pctFilename.toUtf8(), GA_ReadOnly);
121             if(dsPct == nullptr)
122             {
123                 throw tr("Failed to open file with palette.");
124             }
125 
126             GDALRasterBand* band = (GDALRasterBand*)dsPct->GetRasterBand(1);
127 
128             if((dsPct->GetRasterCount() != 1) || (band->GetColorInterpretation() != GCI_PaletteIndex))
129             {
130                 GDALClose(dsPct);
131                 throw tr("Palette file does not have a single band with a color table");
132             }
133 
134             int ok = 0;
135             band->GetNoDataValue(&ok);
136 
137             if(ok || band->GetColorTable()->GetColorEntryCount() > 255)
138             {
139                 GDALClose(dsPct);
140                 throw tr("The color table must not contain a \"no data\" value and it's size must not exceed 255 colors.");
141             }
142 
143             ct = dsPct->GetRasterBand(1)->GetColorTable()->Clone();
144         }
145     }
146     catch(const QString& msg)
147     {
148         delete ct;
149         throw msg;
150     }
151     return ct;
152 }
153 
saveColorTable(GDALColorTable * ct,QString & sctFilename)154 void CApp::saveColorTable(GDALColorTable* ct, QString& sctFilename)
155 {
156     if(sctFilename.isEmpty())
157     {
158         return;
159     }
160 
161     if(!sctFilename.endsWith(".vrt"))
162     {
163         sctFilename += ".vrt";
164     }
165 
166     QByteArray buf = sctFilename.toUtf8();
167     printStdoutQString(tr("Save color table to: %1").arg(buf.data()));
168 
169     GDALDriverManager* drvman = GetGDALDriverManager();
170     GDALDriver* driver = drvman->GetDriverByName("VRT");
171     GDALDataset* dataset = driver->Create(sctFilename.toUtf8(), 1, 1, 1, GDT_Byte, {});
172 
173     dataset->GetRasterBand(1)->SetColorInterpretation(GCI_PaletteIndex);
174     dataset->GetRasterBand(1)->SetColorTable(ct);
175 
176     dataset->FlushCache();
177     GDALClose(dataset);
178 }
179 
ditherMap(GDALDataset * dsSrc,const QString & tarFilename,GDALColorTable * ct)180 void CApp::ditherMap(GDALDataset* dsSrc, const QString& tarFilename, GDALColorTable* ct)
181 {
182     if(tarFilename.isEmpty())
183     {
184         return;
185     }
186 
187     qint32 xsize = dsSrc->GetRasterBand(1)->GetXSize();
188     qint32 ysize = dsSrc->GetRasterBand(1)->GetYSize();
189 
190     GDALDriverManager* drvman = nullptr;
191     GDALDriver* driver = nullptr;
192     GDALDataset* dataset = nullptr;
193 
194     try
195     {
196         const char* cargs[] = {"TILED=YES", "COMPRESS=LZW", 0};
197         drvman = GetGDALDriverManager();
198         driver = drvman->GetDriverByName("GTiff");
199         dataset = driver->Create(tarFilename.toUtf8(), xsize, ysize, 1, GDT_Byte, (char**)cargs);
200 
201         if(dataset == nullptr)
202         {
203             throw tr("Failed to create target file.");
204         }
205 
206         dataset->GetRasterBand(1)->SetColorTable(ct);
207         dataset->GetRasterBand(1)->SetNoDataValue(ct->GetColorEntryCount());
208         dataset->SetProjection(dsSrc->GetProjectionRef());
209 
210         double adfGeoTransform[6] = {0};
211         dsSrc->GetGeoTransform(adfGeoTransform);
212         dataset->SetGeoTransform(adfGeoTransform);
213 
214         printStdoutQString(tr("Dither source file to target file"));
215         int res = GDALDitherRGB2PCT(dsSrc->GetRasterBand(1),
216                                     dsSrc->GetRasterBand(2),
217                                     dsSrc->GetRasterBand(3),
218                                     dataset->GetRasterBand(1),
219                                     ct,
220                                     GDALTermProgress,
221                                     0
222                                     );
223         if(res != CE_None)
224         {
225             throw tr("Failed to dither file.");
226         }
227 
228         if(dsSrc->GetRasterCount() == 3)
229         {
230             return;
231         }
232 
233         GDALRasterBand* alpha = dsSrc->GetRasterBand(4);
234         GDALRasterBand* band = dataset->GetRasterBand(1);
235 
236         QByteArray buffer1(xsize, 0);
237         QByteArray buffer2(xsize, 0);
238 
239         quint8 nodata = band->GetNoDataValue();
240         printStdoutQString(tr("Apply alpha channel as no data value to target file"));
241         for(int y = 0; y < ysize; y++)
242         {
243             GDALTermProgress(double(xsize * y) / (xsize * ysize), 0, 0);
244             res = alpha->RasterIO(GF_Read, 0, y, xsize, 1, buffer1.data(), xsize, 1, GDT_Byte, 0, 0);
245             if(res != CE_None)
246             {
247                 throw tr("Failed to read from alpha channel.");
248             }
249 
250             res = band->RasterIO(GF_Read, 0, y, xsize, 1, buffer2.data(), xsize, 1, GDT_Byte, 0, 0);
251             if(res != CE_None)
252             {
253                 throw tr("Failed to read from target file.");
254             }
255 
256             for(int x = 0; x < xsize; x++)
257             {
258                 if(buffer1[x] != char(0xFF))
259                 {
260                     buffer2[x] = nodata;
261                 }
262             }
263 
264             res = band->RasterIO(GF_Write, 0, y, xsize, 1, buffer2.data(), xsize, 1, GDT_Byte, 0, 0);
265             if(res != CE_None)
266             {
267                 throw tr("Failed to write to target file.");
268             }
269         }
270         GDALTermProgress(1.0, 0, 0);
271     }
272     catch(const QString& msg)
273     {
274         GDALClose(dataset);
275         throw msg;
276     }
277 
278     dataset->FlushCache();
279     GDALClose(dataset);
280 }
281