How to resize YUV image in CUDA - c++

How can I resize a YUV image in CUDA? I tried converting libyuv’s scaling code to CUDA, but the performance is very bad.
void ScalePlaneSimple(int src_width, int src_height,
int dst_width, int dst_height,
int src_stride, int dst_stride,
const Npp8u* src_ptr, Npp8u* dst_ptr) {
int i;
// Initial source x/y coordinate and step values as 16.16 fixed point.
int x = 0;
int y = 0;
int dx = 0;
int dy = 0;
ScaleSlope(src_width, src_height, dst_width, dst_height, kFilterNone,
&x, &y, &dx, &dy);
src_width = Abs(src_width);
if (src_width * 2 == dst_width && x < 0x8000) {
for (i = 0; i < dst_height; ++i) {
ScaleColsUp2_C(dst_ptr, src_ptr + (y >> 16) * src_stride, dst_width, x, dx);
dst_ptr += dst_stride;
y += dy;
}
}
else
{
for (i = 0; i < dst_height; ++i) {
ScaleCols_C<<<1,1>>>(dst_ptr, src_ptr + (y >> 16) * src_stride, dst_width, x, dx);
dst_ptr += dst_stride;
y += dy;
}
}
}
__global__ void ScaleCols_C(Npp8u* dst_ptr, const Npp8u* src_ptr,
int dst_width, int x, int dx) {
int j;
for (j = 0; j < dst_width - 1; j += 2) {
dst_ptr[0] = src_ptr[x >> 16];
x += dx;
dst_ptr[1] = src_ptr[x >> 16];
x += dx;
dst_ptr += 2;
}
if (dst_width & 1) {
dst_ptr[0] = src_ptr[x >> 16];
}
}
Maybe I should use parallel computing? Any advice is welcome.

If you want to use cuda, take a look at the NVidia performance primitives. There are image resizing functions. (It's a copy of the interface for the Intel performance primitives, if you don't want to use the gpu)

Related

Implement bokeh blur with C++

We would like to perform bokeh blur on a image. I have tried to test some code below but could not get Circle of Confusion on bright point.
void bokeh(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride, int Radius)
{
int Channels = Stride / Width;
int rsq = fmax(1, sqrtf(Radius));
for (int y = 0; y < Height; y++)
{
unsigned char * LinePD = Output + y*Stride;
for (int x = 0; x < Width; x++)
{
unsigned int sum[3] = { 0 };
unsigned int weightsum = 0;
for (int ny = std::max(0, y - Radius); ny < std::min(y + Radius, Height); ny++)
{
const unsigned char * sampleLine = Input + ny*Stride;
for (int nx = std::max(0, x - Radius); nx < std::min(x + Radius, Width); nx++)
{
if (sqrtf(nx - x) + sqrtf(ny - y) < rsq)
{
const unsigned char * sample = sampleLine + nx*Channels;
const unsigned char&R = sample[0];
const unsigned char&G = sample[1];
const unsigned char&B = sample[2];
float weight = sqrtf((unsigned char)((21627 * R + 21627 * G + 21627 * B) >> 16));
for (int c = 0; c < Channels; c++)
{
sum[c] += weight*sample[c];
}
weightsum += weight;
}
}
}
for (int c = 0; c < Channels; c++)
{
LinePD[c] = ClampToByte(sum[c] / weightsum);
}
LinePD += Channels;
}
}
}
The source image is:
The result is:
while I expect effect is which like circular in pictures below
seems that I replace sqrtf(nx - x) + sqrtf(ny - y) < rsq
with
powf(nx - x, 2.0) + powf(ny - y, 2.0) < powf(Radius, 2)
and replace float weight = sqrtf((unsigned char)((21627 * R + 21627 * G + 21627 * B) >> 16));
with
float weight = (R + G + B)*1.0f/3.0f;
I could get bokeh blur effect, so how to set the weight to by brightness?

Scaling png font down

Is there a way to scale down with highest quality a font which is png image in opengl at startup? I tried gluScaleImage but there are many artefacts. Is there anything that uses lanczos or something like that? I don't want to write a shader or anything that does the scaling runtime.
This is based on an algorithm, I copied decades ago from the German c't Magazin, and still use it from time to time for similar issues like described by OP.
bool scaleDown(
const Image &imgSrc,
Image &imgDst,
int w, int h,
int align)
{
const int wSrc = imgSrc.w(), hSrc = imgSrc.h();
assert(w > 0 && w <= wSrc && h > 0 && h <= hSrc);
// compute scaling factors
const double sx = (double)wSrc / (double)w;
const double sy = (double)hSrc / (double)h;
const double sxy = sx * sy;
// prepare destination image
imgDst.resize(w, h, (w * 3 + align - 1) / align * align);
// cache some data
const uint8 *const dataSrc = imgSrc.data();
const int bPRSrc = imgSrc.bPR();
// perform scaling
for (int y = 0; y < h; ++y) {
const double yStart = sy * y;
const double yEnd = std::min(sy * (y + 1), (double)hSrc);
const int yStartInt = (int)yStart;
const int yEndInt = (int)yEnd - (yEndInt == yEnd);
const double tFrm = 1 + yStartInt - yStart, bFrm = yEnd - yEndInt;
for (int x = 0; x < w; ++x) {
const double xStart = sx * x;
const double xEnd = std::min(sx * (x + 1), (double)wSrc);
const int xStartInt = (int)xStart;
const int xEndInt = (int)xEnd - (xEndInt == xEnd);
double lFrm = 1 + xStartInt - xStart, rFrm = xEnd - xEndInt;
double pixel[3] = { 0.0, 0.0, 0.0 }; // values of target pixel
for (int i = yStartInt; i <= yEndInt; ++i) {
int jData = i * bPRSrc + xStartInt * 3;
for (int j = xStartInt; j <= xEndInt; ++j) {
double pixelAdd[3];
for (int k = 0; k < 3; ++k) {
pixelAdd[k] = (double)dataSrc[jData++] / sxy;
}
if (j == xStartInt) {
for (int k = 0; k < 3; ++k) pixelAdd[k] *= lFrm;
} else if (j == xEndInt) {
for (int k = 0; k < 3; ++k) pixelAdd[k] *= rFrm;
}
if (i == yStartInt) {
for (int k = 0; k < 3; ++k) pixelAdd[k] *= tFrm;
} else if (i == yEndInt) {
for (int k = 0; k < 3; ++k) pixelAdd[k] *= bFrm;
}
for (int k = 0; k < 3; ++k) pixel[k] += pixelAdd[k];
}
}
imgDst.setPixel(x, y,
(uint8)pixel[0], (uint8)pixel[1], (uint8)pixel[2]);
}
}
// done
return true;
}
If I got it right, this implements a bilinear interpolation.
I don't dare to call it a Minimal Complete Verifiable Example although this is what I intended to do.
The complete sample application:
A simplified class Image
image.h:
#ifndef IMAGE_H
#define IMAGE_H
#include <vector>
// convenience type for bytes
typedef unsigned char uint8;
// image helper class
class Image {
private: // variables:
int _w, _h; // image size
size_t _bPR; // bytes per row
std::vector<uint8> _data; // image data
public: // methods:
// constructor.
Image(): _w(0), _h(0), _bPR(0) { }
// destructor.
~Image() = default;
// copy constructor.
Image(const Image&) = delete; // = default; would work as well.
// copy assignment.
Image& operator=(const Image&) = delete; // = default; would work as well.
// returns width of image.
int w() const { return _w; }
// returns height of image.
int h() const { return _h; }
// returns bytes per row.
size_t bPR() const { return _bPR; }
// returns pointer to image data.
const uint8* data(
int y = 0) // row number
const {
return &_data[y * _bPR];
}
// returns data size (in bytes).
size_t size() const { return _data.size(); }
// clears image.
void clear();
// resizes image.
uint8* resize( // returns allocated buffer
int w, // image width
int h, // image height
int bPR); // bytes per row
// returns pixel.
int getPixel(
int x, // column
int y) // row
const;
// sets pixel.
void setPixel(
int x, // column
int y, // row
uint8 r, uint8 g, uint8 b);
// sets pixel.
void setPixel(
int x, // column
int y, // row
int value) // RGB value
{
setPixel(x, y, value & 0xff, value >> 8 & 0xff, value >> 16 & 0xff);
}
};
// helper functions:
inline uint8 getR(int value) { return value & 0xff; }
inline uint8 getG(int value) { return value >> 8 & 0xff; }
inline uint8 getB(int value) { return value >> 16 & 0xff; }
#endif // IMAGE_H
image.cc:
#include <cassert>
#include "image.h"
// clears image.
void Image::clear()
{
_data.clear(); _w = _h = _bPR = 0;
}
// allocates image data.
uint8* Image::resize( // returns allocated buffer
int w, // image width
int h, // image height
int bPR) // bits per row
{
assert(w >= 0 && 3 * w <= bPR);
assert(h >= 0);
_w = w; _h = h; _bPR = bPR;
const size_t size = h * bPR;
_data.resize(size);
return _data.data();
}
// returns pixel.
int Image::getPixel(
int x, // column
int y) // row
const {
assert(x >= 0 && x < _w);
assert(y >= 0 && y < _h);
const size_t offs = y * _bPR + 3 * x;
return _data[offs + 0]
| _data[offs + 1] << 8
| _data[offs + 2] << 16;
}
// sets pixel.
void Image::setPixel(
int x, // column
int y, // row
uint8 r, uint8 g, uint8 b) // R, G, B values
{
assert(x >= 0 && x < _w);
assert(y >= 0 && y < _h);
const size_t offs = y * _bPR + 3 * x;
_data[offs + 0] = r;
_data[offs + 1] = g;
_data[offs + 2] = b;
}
Image Scaling
imageScale.h:
#ifndef IMAGE_SCALE_H
#define IMAGE_SCALE_H
#include "image.h"
/* scales an image to a certain width and height.
*
* Note:
* imgSrc and imgDst may not be identical.
*/
bool scaleTo( // returns true if successful
const Image &imgSrc, // source image
Image &imgDst, // destination image
int w, int h, // destination width and height
int align = 4); // row alignment
/* scales an image about a certain horizontal/vertical scaling factor.
*
* Note:
* imgSrc and imgDst may not be identical.
*/
inline bool scaleXY( // returns true if successful
const Image &imgSrc, // source image
Image &imgDst, // destination image
double sX, // horizontal scaling factor (must be > 0 but not too large)
double sY, // vertical scaling factor (must be > 0 but not too large)
int align = 4) // row alignment
{
return sX > 0.0 && sY > 0.0
? scaleTo(imgSrc, imgDst,
(int)(sX * imgSrc.w()), (int)(sY * imgSrc.h()), align)
: false;
}
/* scales an image about a certain scaling factor.
*
* Note:
* imgSrc and imgDst may not be identical.
*/
inline bool scale( // returns true if successful
const Image &imgSrc, // source image
Image &imgDst, // destination image
double s, // scaling factor (must be > 0 but not too large)
int align = 4) // row alignment
{
return scaleXY(imgSrc, imgDst, s, s, align);
}
#endif // IMAGE_SCALE_H
imageScale.cc:
#include <cassert>
#include <algorithm>
#include "imageScale.h"
namespace {
template <typename VALUE>
VALUE clip(VALUE value, VALUE min, VALUE max)
{
return value < min ? min : value > max ? max : value;
}
bool scaleDown(
const Image &imgSrc,
Image &imgDst,
int w, int h,
int align)
{
const int wSrc = imgSrc.w(), hSrc = imgSrc.h();
assert(w > 0 && w <= wSrc && h > 0 && h <= hSrc);
// compute scaling factors
const double sx = (double)wSrc / (double)w;
const double sy = (double)hSrc / (double)h;
const double sxy = sx * sy;
// prepare destination image
imgDst.resize(w, h, (w * 3 + align - 1) / align * align);
// cache some data
const uint8 *const dataSrc = imgSrc.data();
const int bPRSrc = imgSrc.bPR();
// perform scaling
for (int y = 0; y < h; ++y) {
const double yStart = sy * y;
const double yEnd = std::min(sy * (y + 1), (double)hSrc);
const int yStartInt = (int)yStart;
const int yEndInt = (int)yEnd - (yEndInt == yEnd);
const double tFrm = 1 + yStartInt - yStart, bFrm = yEnd - yEndInt;
for (int x = 0; x < w; ++x) {
const double xStart = sx * x;
const double xEnd = std::min(sx * (x + 1), (double)wSrc);
const int xStartInt = (int)xStart;
const int xEndInt = (int)xEnd - (xEndInt == xEnd);
double lFrm = 1 + xStartInt - xStart, rFrm = xEnd - xEndInt;
double pixel[3] = { 0.0, 0.0, 0.0 }; // values of target pixel
for (int i = yStartInt; i <= yEndInt; ++i) {
int jData = i * bPRSrc + xStartInt * 3;
for (int j = xStartInt; j <= xEndInt; ++j) {
double pixelAdd[3];
for (int k = 0; k < 3; ++k) {
pixelAdd[k] = (double)dataSrc[jData++] / sxy;
}
if (j == xStartInt) {
for (int k = 0; k < 3; ++k) pixelAdd[k] *= lFrm;
} else if (j == xEndInt) {
for (int k = 0; k < 3; ++k) pixelAdd[k] *= rFrm;
}
if (i == yStartInt) {
for (int k = 0; k < 3; ++k) pixelAdd[k] *= tFrm;
} else if (i == yEndInt) {
for (int k = 0; k < 3; ++k) pixelAdd[k] *= bFrm;
}
for (int k = 0; k < 3; ++k) pixel[k] += pixelAdd[k];
}
}
imgDst.setPixel(x, y,
(uint8)pixel[0], (uint8)pixel[1], (uint8)pixel[2]);
}
}
// done
return true;
}
bool scaleUp(
const Image &imgSrc,
Image &imgDst,
int w, int h,
int align)
{
const int wSrc = imgSrc.w(), hSrc = imgSrc.h();
assert(w && w >= wSrc && h && h >= hSrc);
// compute scaling factors
const double sx = (double)wSrc / (double)w;
const double sy = (double)hSrc / (double)h;
// prepare destination image
imgDst.resize(w, h, (w * 3 + align - 1) / align * align);
// cache some data
const uint8 *const dataSrc = imgSrc.data();
const int bPRSrc = imgSrc.bPR();
// perform scaling
for (int y = 0; y < h; ++y) {
const double yStart = sy * y;
const double yEnd = std::min(sy * (y + 1), (double)hSrc - 1);
const int yStartInt = (int)yStart;
const int yEndInt = (int)yEnd;
if (yStartInt < yEndInt) {
const double bFract = clip((double)((yEnd - yEndInt) / sy), 0.0, 1.0);
const double tFract = 1.0 - bFract;
for (int x = 0; x < w; ++x) {
const double xStart = sx * x;
const double xEnd = std::min(sx * (x + 1), (double)wSrc - 1);
const int xStartInt = (int)xStart, xEndInt = (int)xEnd;
double pixel[4];
if (xStartInt < xEndInt) {
const double rFract
= clip((double)((xEnd - xEndInt) / sx), 0.0, 1.0);
const double lFract = 1.0 - rFract;
int jData = yStartInt * bPRSrc + xStartInt * 3;
for (int k = 0; k < 3; ++k) {
pixel[k] = tFract * lFract * dataSrc[jData++];
}
for (int k = 0; k < 3; ++k) {
pixel[k] += tFract * rFract * dataSrc[jData++];
}
jData = yEndInt * bPRSrc + xStartInt * 3;
for (int k = 0; k < 3; ++k) {
pixel[k] += bFract * lFract *dataSrc[jData++];
}
for (int k = 0; k < 3; ++k) {
pixel[k] += bFract * rFract *dataSrc[jData++];
}
} else {
int jData = yStartInt * bPRSrc + xStartInt * 3;
for (int k = 0; k < 3; ++k) {
pixel[k] = tFract * dataSrc[jData++];
}
jData = yEndInt * bPRSrc + xStartInt * 3;
for (int k = 0; k < 3; ++k) {
pixel[k] += bFract * dataSrc[jData++];
}
}
imgDst.setPixel(x, y,
(uint8)pixel[0], (uint8)pixel[1], (uint8)pixel[2]);
}
} else {
for (int x = 0; x < w; ++x) {
const double xStart = sx * x;
const double xEnd = std::min(sx * (x + 1), (double)wSrc - 1);
const int xStartInt = (int)xStart, xEndInt = (int)xEnd;
double pixel[3];
if (xStartInt < xEndInt) {
const double rFract
= clip((double)((xEnd - xEndInt) / sx), 0.0, 1.0);
const double lFract = 1.0 - rFract;
int jData = yStartInt * bPRSrc + xStartInt * 3;
for (int k = 0; k < 3; ++k) {
pixel[k] = lFract * dataSrc[jData++];
}
for (int k = 0; k < 3; ++k) {
pixel[k] += rFract * dataSrc[jData++];
}
} else {
int jData = yStartInt * bPRSrc + xStartInt * 3;
for (int k = 0; k < 3; ++k) pixel[k] = dataSrc[jData++];
}
imgDst.setPixel(x, y,
(uint8)pixel[0], (uint8)pixel[1], (uint8)pixel[2]);
}
}
}
// done
return true;
}
} // namespace
bool scaleTo(const Image &imgSrc, Image &imgDst, int w, int h, int align)
{
Image imgTmp;
return w <= 0 || h <= 0 ? false
: w >= imgSrc.w() && h >= imgSrc.h()
? scaleUp(imgSrc, imgDst, w, h, align)
: w <= imgSrc.w() && h <= imgSrc.h()
? scaleDown(imgSrc, imgDst, w, h, align)
: w >= imgSrc.w()
? scaleUp(imgSrc, imgTmp, w, imgSrc.h(), 1)
&& scaleDown(imgTmp, imgDst, w, h, align)
: scaleDown(imgSrc, imgTmp, w, imgSrc.h(), 1)
&& scaleUp(imgTmp, imgDst, w, h, align);
}
PPM file IO
imagePPM.h:
#ifndef IMAGE_PPM_H
#define IMAGE_PPM_H
#include <iostream>
#include "image.h"
// reads a binary PPM file.
bool readPPM( // returns true if successful
std::istream &in, // input stream (must be opened with std::ios::binary)
Image &img, // image to read into
int align = 4); // row alignment
// writes binary PPM file.
bool writePPM( // returns true if successful
std::ostream &out, // output stream (must be opened with std::ios::binary)
const Image &img); // image to write from
#endif // IMAGE_PPM_H
imagePPM.cc:
#include <sstream>
#include <string>
#include "imagePPM.h"
// reads a binary PPM file.
bool readPPM( // returns true if successful
std::istream &in, // input stream (must be opened with std::ios::binary)
Image &img, // image to read into
int align) // row alignment
{
// parse header
std::string buffer;
if (!getline(in, buffer)) return false;
if (buffer != "P6") {
std::cerr << "Wrong header! 'P6' expected.\n";
return false;
}
int w = 0, h = 0, t = 0;
for (int i = 0; i < 3;) {
if (!getline(in, buffer)) return false;
if (buffer.empty()) continue; // skip empty lines
if (buffer[0] == '#') continue; // skip comments
std::istringstream str(buffer);
switch (i) {
case 0:
if (!(str >> w)) continue;
++i;
case 1:
if (!(str >> h)) continue;
++i;
case 2:
if (!(str >> t)) continue;
++i;
}
}
if (t != 255) {
std::cerr << "Unsupported format! t = 255 expected.\n";
return false;
}
// allocate image buffer
uint8 *data = img.resize(w, h, (w * 3 + align - 1) / align * align);
// read data
for (int i = 0; i < h; ++i) {
if (!in.read((char*)data, 3 * img.w())) return false;
data += img.bPR();
}
// done
return true;
}
// writes binary PPM file.
bool writePPM( // returns true if successful
std::ostream &out, // output stream (must be opened with std::ios::binary)
const Image &img) // image to write from
{
// write header
if (!(out << "P6\n" << img.w() << ' ' << img.h() << " 255\n")) return false;
// write image data
for (size_t y = 0; y < img.h(); ++y) {
const uint8 *const data = img.data(y);
if (!out.write((const char*)data, 3 * img.w())) return false;
}
// done
return true;
}
The main application
scaleRGBImg.cc:
#include <iostream>
#include <fstream>
#include <string>
#include "image.h"
#include "imagePPM.h"
#include "imageScale.h"
int main(int argc, char **argv)
{
// read command line arguments
if (argc <= 3) {
std::cerr << "Missing arguments!\n";
std::cout
<< "Usage:\n"
<< " scaleRGBImg IN_FILE SCALE OUT_FILE\n";
return 1;
}
const std::string inFile = argv[1];
char *end;
const double s = std::strtod(argv[2], &end);
if (end == argv[2] || *end != '\0') {
std::cerr << "Invalid scale factor '" << argv[2] << "'!\n";
return 1;
}
if (s <= 0.0) {
std::cerr << "Invalid scale factor " << s << "!\n";
return 1;
}
const std::string outFile = argv[3];
// read image
Image imgSrc;
{ std::ifstream fIn(inFile.c_str(), std::ios::binary);
if (!readPPM(fIn, imgSrc)) {
std::cerr << "Reading '" << inFile << "' failed!\n";
return 1;
}
}
// scale image
Image imgDst;
if (!scale(imgSrc, imgDst, s)) {
std::cerr << "Scaling failed!\n";
return 1;
}
// write image
{ std::ofstream fOut(outFile.c_str(), std::ios::binary);
if (!writePPM(fOut, imgDst) || (fOut.close(), !fOut.good())) {
std::cerr << "Writing '" << outFile << "' failed!\n";
return 1;
}
}
// done
return 0;
}
Test
Compiled in cygwin64:
$ g++ -std=c++11 -o scaleRGBImg scaleRGBImg.cc image.cc imagePPM.cc imageScale.cc
$
A sample image test.ppm for a test – converted to PPM in GIMP:
Test with the sample image:
$ for I in 0.8 0.6 0.4 0.2 ; do echo ./scaleRGBImg test.ppm $I test.$I.ppm ; done
./scaleRGBImg test.ppm 0.8 test.0.8.ppm
./scaleRGBImg test.ppm 0.6 test.0.6.ppm
./scaleRGBImg test.ppm 0.4 test.0.4.ppm
./scaleRGBImg test.ppm 0.2 test.0.2.ppm
$ for I in 0.8 0.6 0.4 0.2 ; do ./scaleRGBImg test.ppm $I test.$I.ppm ; done
$
This is what came out:
test.0.8.ppm:
test.0.6.ppm:
test.0.4.ppm:
test.0.2.ppm:

C++ Blur effect on bit map is working but colors are changed

In relation to my previous question BitMap_blur efect, i have succeeded to make the bit map blurred but the problem is the colors of the blurred picture has been changed:
Original photo: https://ibb.co/eFHg8G
Blurred photo: https://ibb.co/mQDShb
The code of the blurring algorytm is the same as in my previous question:
for (xx = 0; xx < bitmapInfoHeader.biWidth; xx++)
{
for (yy = 0; yy <bitmapInfoHeader.biHeight; yy++)
{
avgB = avgG = avgR = 0;
Counter = 0;
for (x = xx; x < bitmapInfoHeader.biWidth && x < xx + blurSize; x++)
{
for (y = yy; y < bitmapInfoHeader.biHeight && y < yy + blurSize; y++)
{
avgB += bitmapImage[x *3 + y*bitmapInfoHeader.biWidth * 3 + 0]; //bitmapimage[x][y];
avgG += bitmapImage[x *3 + y*bitmapInfoHeader.biWidth * 3 + 1];
avgR += bitmapImage[x *3 + y*bitmapInfoHeader.biWidth * 3 + 2];
Counter++;
}
}
avgB = avgB / Counter;
avgG = avgG / Counter;
avgR = avgR / Counter;
bitmapImage[xx * 3 + yy*bitmapInfoHeader.biWidth * 3 + 0] = avgB;
bitmapImage[xx * 3 + yy*bitmapInfoHeader.biWidth * 3 + 1] = avgG;
bitmapImage[xx * 3 + yy*bitmapInfoHeader.biWidth * 3 + 2] = avgR;
}
}
So what am doing wrong here?
It actually looks like size of each line is padded to be multiple of 4 bytes. To get correct byte offset of each line you will need to replace
* bitmapInfoHeader.biWidth * 3
with
* (bitmapInfoHeader.biWidth * 3 + padding_bytes_count)
where
padding_bytes_count =
(
(
bitmapFileHeader.bfSize - bitmapFileHeader.bfOffBits
-
bitmapInfoHeader.biWidth * bitmapInfoHeader.biHeight * 3
)
/
bitmapInfoHeader.biHeight
);
For your tiger image padding_bytes_count should be 2.
Here, I create a semi-portable bitmap reader/writer.. Works on Windows, Linux Mint, MacOS High Sierra. I didn't test other platforms.. but it should work.
It has:
Portability
Load 24-bit bitmaps.
Load 32-bit bitmaps.
Write 24-bit bitmaps.
Write 32-bit bitmaps.
Convert between 24-bit and 32-bit bitmaps.
Convert between 32-bit and 24-bit bitmaps.
It doesn't have:
Support for Alpha Transparency. Alpha transparency has special fields and flags required to be set in the header. I don't feel like writing them in so it won't support it.
Only part of it that doesn't seem very portable would be the #pragma pack..
#include <iostream>
#include <fstream>
#if defined(_WIN32) || defined(_WIN64)
#include <windows.h>
#endif
typedef struct
{
uint8_t r, g, b, a;
} rgb32;
#if !defined(_WIN32) && !defined(_WIN64)
#pragma pack(2)
typedef struct
{
uint16_t bfType;
uint32_t bfSize;
uint16_t bfReserved1;
uint16_t bfReserved2;
uint32_t bfOffBits;
} BITMAPFILEHEADER;
#pragma pack()
#pragma pack(2)
typedef struct
{
uint32_t biSize;
int32_t biWidth;
int32_t biHeight;
uint16_t biPlanes;
uint16_t biBitCount;
uint32_t biCompression;
uint32_t biSizeImage;
int16_t biXPelsPerMeter;
int16_t biYPelsPerMeter;
uint32_t biClrUsed;
uint32_t biClrImportant;
} BITMAPINFOHEADER;
#pragma pack()
#endif
#pragma pack(2)
typedef struct
{
BITMAPFILEHEADER bfh;
BITMAPINFOHEADER bih;
} BMPINFO;
#pragma pack()
class bitmap
{
private:
BMPINFO bmpInfo;
uint8_t* pixels;
public:
bitmap(const char* path);
~bitmap();
void save(const char* path, uint16_t bit_count = 24);
rgb32* getPixel(uint32_t x, uint32_t y) const;
void setPixel(rgb32* pixel, uint32_t x, uint32_t y);
uint32_t getWidth() const;
uint32_t getHeight() const;
uint16_t bitCount() const;
};
bitmap::bitmap(const char* path) : bmpInfo(), pixels(nullptr)
{
std::ifstream file(path, std::ios::in | std::ios::binary);
if (file)
{
file.read(reinterpret_cast<char*>(&bmpInfo.bfh), sizeof(bmpInfo.bfh));
if (bmpInfo.bfh.bfType != 0x4d42)
{
throw std::runtime_error("Invalid format. Only bitmaps are supported.");
}
file.read(reinterpret_cast<char*>(&bmpInfo.bih), sizeof(bmpInfo.bih));
if (bmpInfo.bih.biCompression != 0)
{
std::cerr<<bmpInfo.bih.biCompression<<"\n";
throw std::runtime_error("Invalid bitmap. Only uncompressed bitmaps are supported.");
}
if (bmpInfo.bih.biBitCount != 24 && bmpInfo.bih.biBitCount != 32)
{
throw std::runtime_error("Invalid bitmap. Only 24bit and 32bit bitmaps are supported.");
}
file.seekg(bmpInfo.bfh.bfOffBits, std::ios::beg);
pixels = new uint8_t[bmpInfo.bfh.bfSize - bmpInfo.bfh.bfOffBits];
file.read(reinterpret_cast<char*>(&pixels[0]), bmpInfo.bfh.bfSize - bmpInfo.bfh.bfOffBits);
uint8_t* temp = new uint8_t[bmpInfo.bih.biWidth * bmpInfo.bih.biHeight * sizeof(rgb32)];
uint8_t* in = pixels;
rgb32* out = reinterpret_cast<rgb32*>(temp);
int padding = bmpInfo.bih.biBitCount == 24 ? ((bmpInfo.bih.biSizeImage - bmpInfo.bih.biWidth * bmpInfo.bih.biHeight * 3) / bmpInfo.bih.biHeight) : 0;
for (int i = 0; i < bmpInfo.bih.biHeight; ++i, in += padding)
{
for (int j = 0; j < bmpInfo.bih.biWidth; ++j)
{
out->b = *(in++);
out->g = *(in++);
out->r = *(in++);
out->a = bmpInfo.bih.biBitCount == 32 ? *(in++) : 0xFF;
++out;
}
}
delete[] pixels;
pixels = temp;
}
}
bitmap::~bitmap()
{
delete[] pixels;
}
void bitmap::save(const char* path, uint16_t bit_count)
{
std::ofstream file(path, std::ios::out | std::ios::binary);
if (file)
{
bmpInfo.bih.biBitCount = bit_count;
uint32_t size = ((bmpInfo.bih.biWidth * bmpInfo.bih.biBitCount + 31) / 32) * 4 * bmpInfo.bih.biHeight;
bmpInfo.bfh.bfSize = bmpInfo.bfh.bfOffBits + size;
file.write(reinterpret_cast<char*>(&bmpInfo.bfh), sizeof(bmpInfo.bfh));
file.write(reinterpret_cast<char*>(&bmpInfo.bih), sizeof(bmpInfo.bih));
file.seekp(bmpInfo.bfh.bfOffBits, std::ios::beg);
uint8_t* out = NULL;
rgb32* in = reinterpret_cast<rgb32*>(pixels);
uint8_t* temp = out = new uint8_t[bmpInfo.bih.biWidth * bmpInfo.bih.biHeight * sizeof(rgb32)];
int padding = bmpInfo.bih.biBitCount == 24 ? ((bmpInfo.bih.biSizeImage - bmpInfo.bih.biWidth * bmpInfo.bih.biHeight * 3) / bmpInfo.bih.biHeight) : 0;
for (int i = 0; i < bmpInfo.bih.biHeight; ++i, out += padding)
{
for (int j = 0; j < bmpInfo.bih.biWidth; ++j)
{
*(out++) = in->b;
*(out++) = in->g;
*(out++) = in->r;
if (bmpInfo.bih.biBitCount == 32)
{
*(out++) = in->a;
}
++in;
}
}
file.write(reinterpret_cast<char*>(&temp[0]), size); //bmpInfo.bfh.bfSize - bmpInfo.bfh.bfOffBits
delete[] temp;
}
}
rgb32* bitmap::getPixel(uint32_t x, uint32_t y) const
{
rgb32* temp = reinterpret_cast<rgb32*>(pixels);
return &temp[(bmpInfo.bih.biHeight - 1 - y) * bmpInfo.bih.biWidth + x];
}
void bitmap::setPixel(rgb32* pixel, uint32_t x, uint32_t y)
{
rgb32* temp = reinterpret_cast<rgb32*>(pixels);
memcpy(&temp[(bmpInfo.bih.biHeight - 1 - y) * bmpInfo.bih.biWidth + x], pixel, sizeof(rgb32));
};
uint32_t bitmap::getWidth() const
{
return bmpInfo.bih.biWidth;
}
uint32_t bitmap::getHeight() const
{
return bmpInfo.bih.biHeight;
}
uint16_t bitmap::bitCount() const
{
return bmpInfo.bih.biBitCount;
}
void apply_blur(int x, int y, bitmap* bmp, int blurRadius)
{
double blurValue = 0.111;
int r = 0;
int g = 0 ;
int b = 0;
for (int k = y - blurRadius; k <= blurRadius; ++k)
{
for (int l = x - blurRadius; l <= blurRadius; ++l)
{
rgb32* pixel = bmp->getPixel(l, k);
r += blurValue * pixel->r;
g += blurValue * pixel->g;
b += blurValue * pixel->b;
}
}
rgb32 pixel = *bmp->getPixel(x, y);
pixel.r = r;
pixel.g = g;
pixel.b = b;
bmp->setPixel(&pixel, x, y);
}
int main(int argc, const char * argv[])
{
bitmap bmp{"/Users/brandon/Desktop/tiger.bmp"};
bmp.save("/Users/brandon/Desktop/blurred-tiger-24.bmp");
bmp.save("/Users/brandon/Desktop/blurred-tiger-32.bmp", 32);
return 0;
}
Now all you have to do is add your blur algorithm.. I tried it, but couldn't figure out the blurring part.. I ended up porting an algorithm found here: http://blog.ivank.net/fastest-gaussian-blur.html
void blur(bitmap* bmp, int radius)
{
float rs = ceil(radius * 2.57);
for (int i = 0; i < bmp->getHeight(); ++i)
{
for (int j = 0; j < bmp->getWidth(); ++j)
{
double r = 0, g = 0, b = 0;
double count = 0;
for (int iy = i - rs; iy < i + rs + 1; ++iy)
{
for (int ix = j - rs; ix < j + rs + 1; ++ix)
{
auto x = std::min(static_cast<int>(bmp->getWidth()) - 1, std::max(0, ix));
auto y = std::min(static_cast<int>(bmp->getHeight()) - 1, std::max(0, iy));
auto dsq = ((ix - j) * (ix - j)) + ((iy - i) * (iy - i));
auto wght = std::exp(-dsq / (2.0 * radius * radius)) / (M_PI * 2.0 * radius * radius);
rgb32* pixel = bmp->getPixel(x, y);
r += pixel->r * wght;
g += pixel->g * wght;
b += pixel->b * wght;
count += wght;
}
}
rgb32* pixel = bmp->getPixel(j, i);
pixel->r = std::round(r / count);
pixel->g = std::round(g / count);
pixel->b = std::round(b / count);
}
}
}
int main(int argc, const char * argv[])
{
bitmap bmp{"/Users/brandon/Desktop/tiger.bmp"};
blur(&bmp, 5);
bmp.save("/Users/brandon/Desktop/blurred-tiger.bmp");
return 0;
}
The result becomes:
Since iam only applying the blur effect on 24-bitmaps, I add the padding thing and modified my 3th and 4th loop:
for (x = xx; x < bitmapInfoHeader.biWidth && x < xx + blurSize; **x+=3**)
{
for (y = yy; y < bitmapInfoHeader.biHeight && y < yy + blurSize; **y+=3**)
And it works! the photo still have a weard thin line on the left but i think this is a read/write bitmap problem and i can handle it myself :)
The blurred photo: https://ibb.co/iGp9Cb and another blurred picture: https://ibb.co/jFXUCb
Thank you guys for your answers! it helped alot

Visualizing/saving an extremely large number of pixels with

I made a program in C++ which calculates the mandelbrot-set. Now I want to visualize it (save it in a picture). But when I try to save a 64k picture some problems come up. So what is the best way to save a picture of the pixels or at least to visual it?
Edit:
When I want to create a for Example 64K (61440 * 34560) image there will be the error "Access violation while writing at the position 0x0..." (originally on German and translated) and the program stops. This error appears with very high resolution. On lower resolutions the program works as it is supposed to.
#include <SFML\Graphics.hpp>
#include <stdlib.h>
#include <complex>
#include <cmath>
#include <thread>
//4K : 3840 * 2160
//8K : 7680 * 4320
//16K: 15360 * 8640
//32K: 30720 * 17280
//64K: 61440 * 34560
//128K:122880 * 69120
const unsigned long width = 61440; //should be dividable by ratioX & numberOfThreads!
const unsigned long height = 34560; //should be dividable by ratioY & numberOfThreads!
const unsigned int maxIterations = 500;
const unsigned int numberOfThreads = 6;
const int maxWidth = width / 3;
const int maxHeight = height / 2;
const int minWidth = -maxWidth * 2;
const int minHeight = -maxHeight;
const double ratioX = 3.0 / width;
const double ratioY = 2.0 / height;
sf::Image img = sf::Image();
int getsGreaterThan2(std::complex<double> z, int noIterations) {
double result;
std::complex<double> zTmp = z;
std::complex<double> c = z;
for (int i = 1; i != noIterations; i++) {
zTmp = std::pow(z, 2) + c;
if (zTmp == z) {
return 0;
}
z = std::pow(z, 2) + c;
result = std::sqrt(std::pow(z.real(), 2) + std::pow(z.imag(), 2));
if (result > 2) {
return i;
}
}
return 0;
}
void fillPixelArrayThreadFunc(int noThreads, int threadNr) { //threadNr ... starts from 0
double imgNumber;
double realNumber;
double tmp;
long startWidth = ((double)width) / noThreads * threadNr + minWidth;
long endWidth = startWidth + width / noThreads;
for (long x = startWidth; x < endWidth; x++) {
imgNumber = x * ratioX;
for (long y = minHeight; y < maxHeight; y++) {
realNumber = y * ratioY;
long xArray = x - minWidth;
long yArray = y - minHeight;
tmp = getsGreaterThan2(std::complex<double>(imgNumber, realNumber), maxIterations);
if (tmp == 0) {
img.setPixel(xArray, yArray, sf::Color(0, 0, 0, 255));
}
else {
img.setPixel(xArray, yArray, sf::Color(tmp / maxIterations * 128, tmp / maxIterations * 128, tmp / maxIterations * 255, 255));
}
}
}
}
int main() {
img.create(width, height, sf::Color::Black);
std::thread *threads = new std::thread[numberOfThreads];
for (int i = 0; i < numberOfThreads; i++) {
threads[i] = std::thread(std::bind(fillPixelArrayThreadFunc, numberOfThreads, i));
}
for (int i = 0; i < numberOfThreads; i++) {
threads[i].join();
}
img.saveToFile("filename.png");
return 1;
}
Your program fails during the call img.create(width, height, sf::Color::Black);.
When you step into the sf::Image::create function you end up here where the newPixels vector is created, this simply fails when width * height is too big as in your case:
////////////////////////////////////////////////////////////
void Image::create(unsigned int width, unsigned int height, const Color& color)
{
if (width && height)
{
// Create a new pixel buffer first for exception safety's sake
std::vector<Uint8> newPixels(width * height * 4);
^61440* ^34560 = 8'493'465'600 bytes !!
Conclusion: SFML cannot handle huge images.

Fast calculation of image moments

I have a mask (8-bit gray image) and I need calculate center of region with given index of the mask.
To do this I need calculate moments of first order along axes X and Y for this mask.
Currently I'm using next code:
void GetCenter(const uint8_t * mask, size_t stride, size_t width, size_t height,
uint8_t index, double * centerX, double * centerY)
{
uint64_t sum = 0, sumX = 0, sumY = 0;
for(size_t y = 0; y < height; ++y)
{
for(size_t x = 0; x < width; ++x)
{
if(mask[x] == index)
{
sum++;
sumX += x;
sumY += y;
}
}
mask += stride;
}
*centerX = sum ? (double)sumX/sum : 0;
*centerY = sum ? (double)sumY/sum : 0;
}
And I have a question: Is there any way to improve performance of this algorithm?
There is a way to greatly (more then ten times) improve performance of this algorithm.
To do it you need use SIMD instructions of CPU such as (SSE2, AVX2, Altivec, NEON etc.).
I wrote an example with using of SSE2 instructions (AVX2 code will be similar to it):
const __m128i K_0 = _mm_setzero_si128();
const __m128i K8_1 = _mm_set1_epi8(1);
const __m128i K16_1 = _mm_set1_epi16(1);
const __m128i K16_8 = _mm_set1_epi16(8);
const __m128i K16_I = _mm_setr_epi16(0, 1, 2, 3, 4, 5, 6, 7);
inline void AddMoments(const __m128i & mask, const __m128i & x, const __m128i & y,
__m128i & sumX, __m128i & sumY)
{
sumX = _mm_add_epi32(sumX, _mm_madd_epi16(_mm_and_si128(mask, x), K16_1));
sumY = _mm_add_epi32(sumY, _mm_madd_epi16(_mm_and_si128(mask, y), K16_1));
}
inline int ExtractSum(__m128i a)
{
return _mm_cvtsi128_si32(a) + _mm_cvtsi128_si32(_mm_srli_si128(a, 4)) +
_mm_cvtsi128_si32(_mm_srli_si128(a, 8)) + _mm_cvtsi128_si32(_mm_srli_si128(a, 12));
}
void GetCenter(const uint8_t * mask, size_t stride, size_t width, size_t height,
uint8_t index, double * centerX, double * centerY)
{
size_t alignedWidth = width & ~(sizeof(__m128i) - 1);
const __m128i _index = _mm_set1_epi8(index);
uint64_t sum = 0, sumX = 0, sumY = 0;
for(size_t y = 0; y < height; ++y)
{
size_t x = 0;
__m128i _x = K16_I;
__m128i _y = _mm_set1_epi16((short)y);
__m128i _sum = K_0;
__m128i _sumX = K_0;
__m128i _sumY = K_0;
for(; x < alignedWidth; x += sizeof(__m128i))
{
__m128i _mask = _mm_and_si128(_mm_cmpeq_epi8(_mm_loadu_si128((__m128i*)(mask + x)), _index), K8_1);
_sum = _mm_add_epi64(_sum, _mm_sad_epu8(_mask, K_0));
AddMoments(_mm_cmpeq_epi16(_mm_unpacklo_epi8(_mask, K_0), K16_1), _x, _y, _sumX, _sumY);
_x = _mm_add_epi16(_x, K16_8);
AddMoments(_mm_cmpeq_epi16(_mm_unpackhi_epi8(_mask, K_0), K16_1), _x, _y, _sumX, _sumY);
_x = _mm_add_epi16(_x, K16_8);
}
sum += ExtractSum(_sum);
sumX += ExtractSum(_sumX);
sumY += ExtractSum(_sumY);
for(; x < width; ++x)
{
if(mask[x] == index)
{
sum++;
sumX += x;
sumY += y;
}
}
mask += stride;
}
*centerX = sum ? (double)sumX/sum : 0;
*centerY = sum ? (double)sumY/sum : 0;
}
P.S. There is a more simple and cross platform way to improve performance with using of external library (http://simd.sourceforge.net/):
void GetCenter(const uint8_t * mask, size_t stride, size_t width, size_t height,
uint8_t index, double * centerX, double * centerY)
{
uint64_t sum, sumX, sumY, sumXX, sumXY, sumYY;
::SimdGetMoments(mask, stride, width, height, index,
&sum, &sumX, &sumY, &sumXX, &sumXY, &sumYY);
*centerX = sum ? (double)sumX/sum : 0;
*centerY = sum ? (double)sumY/sum : 0;
}
An implementation with using of _mm_movemask_epi8 and 8-bit lookup tables:
uint8_t g_sum[1 << 8], g_sumX[1 << 8];
bool Init()
{
for(int i = 0, n = 1 << 8; i < n; ++i)
{
g_sum[i] = 0;
g_sumX[i] = 0;
for(int j = 0; j < 8; ++j)
{
g_sum[i] += (i >> j) & 1;
g_sumX[i] += ((i >> j) & 1)*j;
}
}
return true;
}
bool g_inited = Init();
inline void AddMoments(uint8_t mask, size_t x, size_t y,
uint64_t & sum, uint64_t & sumX, uint64_t & sumY)
{
int value = g_sum[mask];
sum += value;
sumX += x * value + g_sumX[mask];
sumY += y * value;
}
void GetCenter(const uint8_t * mask, size_t stride, size_t width, size_t height,
uint8_t index, double * centerX, double * centerY)
{
size_t alignedWidth = width & ~(sizeof(__m128i) - 1);
const __m128i _index = _mm_set1_epi8(index);
union PackedValue
{
uint8_t u8[4];
uint16_t u16[2];
uint32_t u32;
} _mask;
uint64_t sum = 0, sumX = 0, sumY = 0;
for(size_t y = 0; y < height; ++y)
{
size_t x = 0;
for(; x < alignedWidth; x += sizeof(__m128i))
{
_mask.u32 = _mm_movemask_epi8(_mm_cmpeq_epi8(
_mm_loadu_si128((__m128i*)(mask + x)), _index));
AddMoments(_mask.u8[0], x, y, sum, sumX, sumY);
AddMoments(_mask.u8[1], x + 8, y, sum, sumX, sumY);
}
for(; x < width; ++x)
{
if(mask[x] == index)
{
sum++;
sumX += x;
sumY += y;
}
}
mask += stride;
}
*centerX = sum ? (double)sumX/sum : 0;
*centerY = sum ? (double)sumY/sum : 0;
}
An implementation with using of _mm_movemask_epi8 and 16-bit lookup tables:
uint16_t g_sum[1 << 16], g_sumX[1 << 16];
bool Init()
{
for(int i = 0, n = 1 << 16; i < n; ++i)
{
g_sum[i] = 0;
g_sumX[i] = 0;
for(int j = 0; j < 16; ++j)
{
g_sum[i] += (i >> j) & 1;
g_sumX[i] += ((i >> j) & 1)*j;
}
}
return true;
}
bool g_inited = Init();
inline void AddMoments(uint16_t mask, size_t x, size_t y,
uint64_t & sum, uint64_t & sumX, uint64_t & sumY)
{
int value = g_sum[mask];
sum += value;
sumX += x * value + g_sumX[mask];
sumY += y * value;
}
void GetCenter(const uint8_t * mask, size_t stride, size_t width, size_t height,
uint8_t index, double * centerX, double * centerY)
{
size_t alignedWidth = width & ~(sizeof(__m128i) - 1);
const __m128i _index = _mm_set1_epi8(index);
union PackedValue
{
uint8_t u8[4];
uint16_t u16[2];
uint32_t u32;
} _mask;
uint64_t sum = 0, sumX = 0, sumY = 0;
for(size_t y = 0; y < height; ++y)
{
size_t x = 0;
for(; x < alignedWidth; x += sizeof(__m128i))
{
_mask.u32 = _mm_movemask_epi8(_mm_cmpeq_epi8(
_mm_loadu_si128((__m128i*)(mask + x)), _index));
AddMoments(_mask.u16[0], x, y, sum, sumX, sumY);
}
for(; x < width; ++x)
{
if(mask[x] == index)
{
sum++;
sumX += x;
sumY += y;
}
}
mask += stride;
}
*centerX = sum ? (double)sumX/sum : 0;
*centerY = sum ? (double)sumY/sum : 0;
}
Performance comparison for 1920x1080 image:
Base version: 8.261 ms;
1-st optimization:0.363 ms (in 22 times faster);
2-nd optimization:0.280 ms (in 29 times faster);
3-rd optimization:0.299 ms (in 27 times faster);
4-th optimization:0.325 ms (in 25 times faster);
As you can see above the code with using of 8-bit lookup tables has better performance then the code with using of 16-bit lookup tables. But anyway external library is better though it performs additional calculations of the second order moments.
Another acceleration technique is by run-length coding.
You can decompose the rows in horizontal runs where the mask is active. You can detect the runs on the fly, or precompute them and store the image in that form, if that makes sense.
Then a run can be accumulated as a whole. Let a run start from (X, Y) and have length L, then use
Sum+= L;
SumX+= (2 * X + L + 1) * L;
SumY+= Y * L;
In the end, divide SumX by 2.
The longer the runs, the more effective the trick is.
Using SSE2 or later, you try with the instruction PMOVMSKB—Move Byte Mask.
First compare 16 mask pixels to the (replicated) index value to get 16 comparison results. Then pack these to a 16 bits number, using the magical instruction.
Then, using two precomputed lookup tables, perform the accumulations in scalar mode.
One lookup table gives you the count of active mask pixels, and the other gives you the sum of active mask pixels weighted by their abscissa, i.e the X moment.
Something like
int PackedValue= _mm_movemask_epi8(_mm_cmpeq_epi8(_mm_loadu_si128((__m128i*)&Mask[X]), ReplicatedIndex));
Sum+= Count[PackedValue];
SumX+= X * Count[PackedValue] + MomentX[PackedValue];
SumY+= Y * Count[PackedValue];
Depending on the amount of memory you agree to spend, the lookup tables can have byte indexes (256 entries, use the table twice) or word indexes (65536 entries). In both cases, the count and moment values fit in a single byte (1 to 8/16 and 0 to 28/120 respectively).
AVX implementation is also possible, packing 32 pixels at a time. Lookup tables with doubleword indexes seem unreasonable, though. :-)