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