1 // Copyright (C) 2020 by Yuri Victorovich. All rights reserved.
2
3 #include "image.h"
4 #include "tensor.h"
5 #include "misc.h"
6 #include "util.h"
7
8 #include <png++/png.hpp>
9 #include <avir/avir.h>
10
11 #include <QPixmap>
12 #include <QImage>
13
14 #include <string>
15 #include <array>
16 #include <memory>
17 #include <cstring>
18 #include <functional>
19
20 #include <assert.h>
21
22 namespace Image {
23
readPngImageFile(const std::string & fileName,TensorShape & outShape)24 float* readPngImageFile(const std::string &fileName, TensorShape &outShape) {
25 png::image<png::rgb_pixel> image(fileName.c_str());
26 auto width = image.get_width();
27 auto height = image.get_height();
28
29 std::unique_ptr<float> data(new float[width*height*3]);
30
31 float *p = data.get();
32 for (unsigned y = 0; y < height; ++y)
33 for (unsigned x = 0; x < width; ++x) {
34 png::rgb_pixel color = image[y][x];
35 for (auto c : {color.red, color.green, color.blue})
36 *p++ = c;
37 }
38
39 outShape = {height,width,3};
40 return data.release();
41 }
42
writePngImageFile(const float * pixels,const TensorShape & shape,const std::string & fileName)43 void writePngImageFile(const float *pixels, const TensorShape &shape, const std::string &fileName) { // ASSUME 0..255 normalization
44 assert(shape.size()==3);
45 auto width = shape[1];
46 auto height = shape[0];
47
48 png::image<png::rgb_pixel> image(width, height);
49 for (unsigned y = 0; y < height; y++)
50 for (unsigned x = 0; x < width; x++) {
51 png::rgb_pixel &color = image[y][x];
52 color.red = *pixels++;
53 color.green = *pixels++;
54 color.blue = *pixels++;
55 }
56
57 image.write(fileName);
58 }
59
readPixmap(const QPixmap & pixmap,TensorShape & outShape,std::function<void (const std::string &)> cbWarningMessage)60 float* readPixmap(const QPixmap &pixmap, TensorShape &outShape, std::function<void(const std::string&)> cbWarningMessage) {
61 QImage image = pixmap.toImage();
62
63 // always convert image to RGB32 so we don't have to deal with any other formats
64 #if QT_VERSION >= QT_VERSION_CHECK(5,13,0) // QImage::convertTo exists since Qt-5.13
65 image.convertTo(QImage::Format_RGB32, Qt::ColorOnly); // CAVEAT: the alpha channel is converted to black, which isn't normally desirable
66 assert(image.format()==QImage::Format_RGB32);
67 #else
68 if (image.format()!=QImage::Format_RGB32) {
69 cbWarningMessage(STR("Your Qt version " << QT_VERSION_MAJOR << "." << QT_VERSION_MINOR << "." << QT_VERSION_PATCH << " is older than 5.13.0, it is missing QImage::convertTo needed to convert this image to the QImage::Format_RGB32 format"));
70 return nullptr;
71 }
72 #endif
73
74 // TODO for pixmaps with alpha-channel use QImage::Format_ARGB32 and convert alpha to white (or any other background color)
75 if (pixmap.hasAlpha())
76 WARNING("the image has alpha channel which is converted to black")
77
78 std::unique_ptr<float> data(new float[image.width()*image.height()*3]);
79 auto pi = image.bits();
80 auto pf = data.get();
81
82 for (float *pfe = pf+image.width()*image.height()*3; pf < pfe; pi+=4, pf+=3) {
83 pf[0] = pi[2];
84 pf[1] = pi[1];
85 pf[2] = pi[0];
86 }
87
88 outShape = {(unsigned)image.height(), (unsigned)image.width(), 3};
89 return data.release();
90 }
91
resizeImage(const float * pixels,const TensorShape & shapeOld,const TensorShape & shapeNew)92 float* resizeImage(const float *pixels, const TensorShape &shapeOld, const TensorShape &shapeNew) {
93 auto sz = Tensor::flatSize(shapeNew);
94 std::unique_ptr<float> pixelsNew(new float[sz]);
95 avir::CImageResizer<> ImageResizer(8);
96 ImageResizer.resizeImage(
97 pixels,
98 shapeOld[1],
99 shapeOld[0],
100 0,
101 pixelsNew.get(),
102 shapeNew[1],
103 shapeNew[0],
104 shapeNew[2],
105 0);
106
107 // clip values because the resizer leaves some slightly out-of-range (0..255) values
108 for (auto d = pixelsNew.get(), de = d + sz; d < de; d++) {
109 if (*d < 0.)
110 *d = 0.;
111 else if (*d >= 255.)
112 *d = 255.; // - std::numeric_limits<float>::epsilon();
113 }
114
115 return pixelsNew.release();
116 }
117
regionOfImage(const float * pixels,const TensorShape & shape,const std::array<unsigned,4> region)118 float* regionOfImage(const float *pixels, const TensorShape &shape, const std::array<unsigned,4> region) {
119 assert(shape.size()==3);
120 assert(region[0]<=region[2] && region[2]<shape[1]); // W
121 assert(region[1]<=region[3] && region[3]<shape[0]); // H
122
123 unsigned NC = shape[2];
124
125 unsigned regionWidth = region[2]-region[0]+1;
126 unsigned regionHeight = region[3]-region[1]+1;
127 std::unique_ptr<float> result(new float[regionHeight*regionWidth*NC]);
128
129 unsigned skip = shape[1]*NC;
130 unsigned bpl = regionWidth*NC;
131 auto src = pixels+(region[1]*shape[1]+region[0])*NC;
132 auto dst = result.get();
133 for (auto dste = dst+regionWidth*regionHeight*NC; dst<dste; src+=skip, dst+=bpl)
134 std::memcpy(dst, src, bpl*sizeof(float));
135
136 return result.release();
137 }
138
toQPixmap(const float * image,const TensorShape & shape)139 QPixmap toQPixmap(const float *image, const TensorShape &shape) {
140 return QPixmap::fromImage(QImage(
141 std::unique_ptr<const uchar>(Util::convertArrayFloatToUInt8(image, Tensor::flatSize(shape))).get(),
142 shape[1], // width
143 shape[0], // height
144 shape[1]*shape[2], // bytesPerLine
145 QImage::Format_RGB888
146 ));
147 }
148
149 template<typename T>
reverseArray(const T * src,T * dst,unsigned rowSize,unsigned blockSize)150 static void reverseArray(const T *src, T *dst, unsigned rowSize, unsigned blockSize) {
151 dst = dst + rowSize-blockSize;
152 for (auto srce = src + rowSize; src<srce; src+=blockSize, dst-=blockSize)
153 std::memcpy(dst, src, blockSize*sizeof(T));
154 }
155
flipHorizontally(const TensorShape & shape,const float * imgSrc,float * imgDst)156 void flipHorizontally(const TensorShape &shape, const float *imgSrc, float *imgDst) {
157 unsigned rowSize = shape[1]*shape[2];
158 unsigned blockSize = shape[2];
159 for (auto imgSrce = imgSrc + Tensor::flatSize(shape); imgSrc<imgSrce; imgSrc+=rowSize, imgDst+=rowSize)
160 reverseArray(imgSrc, imgDst, rowSize, blockSize);
161 }
162
flipVertically(const TensorShape & shape,const float * imgSrc,float * imgDst)163 void flipVertically(const TensorShape &shape, const float *imgSrc, float *imgDst) {
164 reverseArray(imgSrc, imgDst, Tensor::flatSize(shape), shape[1]*shape[2]);
165 }
166
makeGrayscale(const TensorShape & shape,const float * imgSrc,float * imgDst)167 void makeGrayscale(const TensorShape &shape, const float *imgSrc, float *imgDst) {
168 assert(shape[2]==3);
169 auto convertColor = [](float R, float G, float B) {
170 return (R+G+B)/3;
171 };
172 for (auto imgSrce = imgSrc + Tensor::flatSize(shape); imgSrc<imgSrce; imgSrc+=3, imgDst+=3)
173 imgDst[0] = imgDst[1] = imgDst[2] = convertColor(imgSrc[0], imgSrc[1], imgSrc[2]);
174 }
175
176 }
177