1 /*
2     SPDX-FileCopyrightText: 2021 Jasem Mutlaq <mutlaqja@ikarustech.com>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include <zlib.h>
8 #include <QApplication>
9 
10 #include "fitshistogramcommand.h"
11 #include "fitshistogrameditor.h"
12 #include "fitsviewer.h"
13 #include "fits_debug.h"
14 
FITSHistogramCommand(const QSharedPointer<FITSData> & data,FITSHistogramEditor * inHisto,FITSScale newType,const QVector<double> & lmin,const QVector<double> & lmax)15 FITSHistogramCommand::FITSHistogramCommand(const QSharedPointer<FITSData> &data,
16         FITSHistogramEditor * inHisto,
17         FITSScale newType,
18         const QVector<double> &lmin,
19         const QVector<double> &lmax) : m_ImageData(data), histogram(inHisto),
20     type(newType), min(lmin), max(lmax)
21 {
22 }
23 
~FITSHistogramCommand()24 FITSHistogramCommand::~FITSHistogramCommand()
25 {
26     delete[] delta;
27 }
28 
calculateDelta(const uint8_t * buffer)29 bool FITSHistogramCommand::calculateDelta(const uint8_t * buffer)
30 {
31     uint8_t const * image_buffer = m_ImageData->getImageBuffer();
32     uint32_t totalPixels = m_ImageData->samplesPerChannel() * m_ImageData->channels();
33     unsigned long totalBytes = totalPixels * m_ImageData->getBytesPerPixel();
34 
35     auto * raw_delta = new uint8_t[totalBytes];
36 
37     if (raw_delta == nullptr)
38     {
39         qCWarning(KSTARS_FITS) << "Error! not enough memory to create image delta";
40         return false;
41     }
42 
43     for (uint32_t i = 0; i < totalBytes; i++)
44         raw_delta[i] = buffer[i] ^ image_buffer[i];
45 
46     compressedBytes = sizeof(uint8_t) * totalBytes + totalBytes / 64 + 16 + 3;
47     delete[] delta;
48     delta = new uint8_t[compressedBytes];
49 
50     if (delta == nullptr)
51     {
52         delete[] raw_delta;
53         qCCritical(KSTARS_FITS)
54                 << "FITSHistogram Error: Ran out of memory compressing delta";
55         return false;
56     }
57 
58     int r = compress2(delta, &compressedBytes, raw_delta, totalBytes, 5);
59 
60     if (r != Z_OK)
61     {
62         delete[] raw_delta;
63         /* this should NEVER happen */
64         qCCritical(KSTARS_FITS)
65                 << "FITSHistogram Error: Failed to compress raw_delta";
66         return false;
67     }
68 
69     delete[] raw_delta;
70     return true;
71 }
72 
reverseDelta()73 bool FITSHistogramCommand::reverseDelta()
74 {
75     uint8_t const * image_buffer = m_ImageData->getImageBuffer();
76     uint32_t totalPixels = m_ImageData->samplesPerChannel() * m_ImageData->channels();
77     unsigned long totalBytes = totalPixels * m_ImageData->getBytesPerPixel();
78 
79     auto * output_image = new uint8_t[totalBytes];
80 
81     if (output_image == nullptr)
82     {
83         qCWarning(KSTARS_FITS) << "Error! not enough memory to create output image";
84         return false;
85     }
86 
87     auto * raw_delta = new uint8_t[totalBytes];
88 
89     if (raw_delta == nullptr)
90     {
91         delete[] output_image;
92         qCWarning(KSTARS_FITS) << "Error! not enough memory to create image delta";
93         return false;
94     }
95 
96     int r = uncompress(raw_delta, &totalBytes, delta, compressedBytes);
97     if (r != Z_OK)
98     {
99         qCCritical(KSTARS_FITS)
100                 << "FITSHistogram compression error in reverseDelta()";
101         delete[] output_image;
102         delete[] raw_delta;
103         return false;
104     }
105 
106     for (unsigned int i = 0; i < totalBytes; i++)
107         output_image[i] = raw_delta[i] ^ image_buffer[i];
108 
109     m_ImageData->setImageBuffer(output_image);
110 
111     delete[] raw_delta;
112 
113     return true;
114 }
115 
redo()116 void FITSHistogramCommand::redo()
117 {
118     uint8_t const * image_buffer = m_ImageData->getImageBuffer();
119     uint8_t * buffer = nullptr;
120     uint32_t totalPixels = m_ImageData->samplesPerChannel() * m_ImageData->channels();
121     int BBP = m_ImageData->getBytesPerPixel();
122 
123     QApplication::setOverrideCursor(Qt::WaitCursor);
124 
125     if (delta != nullptr)
126     {
127         FITSImage::Statistic prevStats;
128         m_ImageData->saveStatistics(prevStats);
129 
130         reverseDelta();
131 
132         m_ImageData->restoreStatistics(stats);
133 
134         stats = prevStats;
135     }
136     else
137     {
138         m_ImageData->saveStatistics(stats);
139 
140         // If it's rotation of flip, no need to calculate delta
141         if (type >= FITS_ROTATE_CW && type <= FITS_FLIP_V)
142         {
143             m_ImageData->applyFilter(type);
144         }
145         else
146         {
147             buffer = new uint8_t[totalPixels * BBP];
148 
149             if (buffer == nullptr)
150             {
151                 qWarning(KSTARS_FITS()) << "Error! not enough memory to create image buffer in redo()";
152                 QApplication::restoreOverrideCursor();
153                 return;
154             }
155 
156             memcpy(buffer, image_buffer, totalPixels * BBP);
157 
158             QVector<double> dataMin = min, dataMax = max;
159             switch (type)
160             {
161                 case FITS_AUTO:
162                 case FITS_LINEAR:
163                     m_ImageData->applyFilter(FITS_LINEAR, nullptr, &dataMin, &dataMax);
164                     break;
165 
166                 case FITS_LOG:
167                     m_ImageData->applyFilter(FITS_LOG, nullptr, &dataMin, &dataMax);
168                     break;
169 
170                 case FITS_SQRT:
171                     m_ImageData->applyFilter(FITS_SQRT, nullptr, &dataMin, &dataMax);
172                     break;
173 
174                 default:
175                     m_ImageData->applyFilter(type);
176                     break;
177             }
178 
179             calculateDelta(buffer);
180             delete[] buffer;
181         }
182     }
183 
184     //    if (histogram != nullptr)
185     //    {
186     //        histogram->construct();
187 
188     //        //        if (tab->getViewer()->isStarsMarked())
189     //        //            imageData->findStars().waitForFinished();
190     //    }
191 
192     //    image->pushFilter(type);
193     //    image->rescale(ZOOM_KEEP_LEVEL);
194     //    image->updateFrame();
195 
196     QApplication::restoreOverrideCursor();
197 }
198 
undo()199 void FITSHistogramCommand::undo()
200 {
201     QApplication::setOverrideCursor(Qt::WaitCursor);
202 
203     if (delta != nullptr)
204     {
205         FITSImage::Statistic prevStats;
206         m_ImageData->saveStatistics(prevStats);
207 
208         reverseDelta();
209 
210         m_ImageData->restoreStatistics(stats);
211 
212         stats = prevStats;
213     }
214     else
215     {
216         switch (type)
217         {
218             case FITS_ROTATE_CW:
219                 m_ImageData->applyFilter(FITS_ROTATE_CCW);
220                 break;
221             case FITS_ROTATE_CCW:
222                 m_ImageData->applyFilter(FITS_ROTATE_CW);
223                 break;
224             case FITS_FLIP_H:
225             case FITS_FLIP_V:
226                 m_ImageData->applyFilter(type);
227                 break;
228             default:
229                 break;
230         }
231     }
232 
233     //    if (histogram != nullptr)
234     //    {
235     //        histogram->construct();
236 
237     //        //        if (tab->getViewer()->isStarsMarked())
238     //        //            imageData->findStars().waitForFinished();
239     //    }
240 
241     //    image->popFilter();
242     //    image->rescale(ZOOM_KEEP_LEVEL);
243     //    image->updateFrame();
244 
245     QApplication::restoreOverrideCursor();
246 }
247 
text() const248 QString FITSHistogramCommand::text() const
249 {
250     switch (type)
251     {
252         case FITS_AUTO:
253             return i18n("Auto Scale");
254         case FITS_LINEAR:
255             return i18n("Linear Scale");
256         case FITS_LOG:
257             return i18n("Logarithmic Scale");
258         case FITS_SQRT:
259             return i18n("Square Root Scale");
260 
261         default:
262             if (type - 1 <= FITSViewer::filterTypes.count())
263                 return FITSViewer::filterTypes.at(type - 1);
264             break;
265     }
266 
267     return i18n("Unknown");
268 }
269 
270