XSLXWriter Insert multiple images at center of cell in a column - c++

I am trying to use libxlsxwriter to insert images in a column row by row but the images are out of cell bound and on top of each other.
Edit:
I found that this line of code might be causing some issues worksheet_set_default_row(worksheet,110,true);
and changed my code to the following:
#include <QCoreApplication>
#include "QProcess"
#include "QThread"
#include "xlsxwriter.h"
#include <QtGui/QImage>
#include <QBuffer>
#include<QDebug>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QImage* img = new QImage("F:/Muaz/Programming/Visual C++/Qt/web_browser_open_close/Image0004.jpg");
int pixel_width = int(25 * 7 + 0.5) + 5;
int pixel_height = int(4.0 / 3.0 * 110);
QImage scaled_img =img->scaled(pixel_width,pixel_height,Qt::KeepAspectRatio);
int new_h = scaled_img.height();
int new_w = scaled_img.width();
QByteArray sbytes;
QBuffer buffer(&sbytes);
buffer.open(QIODevice::WriteOnly);
scaled_img.save(&buffer, "jpg");
buffer.close();
unsigned char* data =new unsigned char[sbytes.size()];
memcpy(data,sbytes.constData(),sbytes.size());
QImage* img1 = new QImage("F:/Muaz/Programming/Visual C++/Qt/web_browser_open_close/Image0003.jpg");
QImage scaled_img1 =img1->scaled(pixel_width,pixel_height,Qt::KeepAspectRatio);
qInfo() << "width: " << new_w << ", height: " << new_h;
qInfo() << "width: " << scaled_img1.width() << ", height: " << scaled_img1.height();
QByteArray sbytes1;
QBuffer buffer1(&sbytes1);
buffer1.open(QIODevice::WriteOnly);
scaled_img1.save(&buffer1, "jpg");
buffer1.close();
unsigned char* data1 =new unsigned char[sbytes1.size()];
memcpy(data1,sbytes1.constData(),sbytes1.size());
qInfo() << sbytes.size();
qInfo() << sbytes1.size();
lxw_workbook *workbook = workbook_new("F:/Muaz/Programming/Visual C++/Qt/web_browser_open_close/sample.xlsx");
lxw_worksheet *worksheet = workbook_add_worksheet(workbook, "Shop Items");
worksheet_set_row(worksheet,0,110,NULL);
worksheet_set_row(worksheet,1,110,NULL);
worksheet_set_row(worksheet,2,110,NULL);
worksheet_set_row(worksheet,3,110,NULL);
worksheet_set_column(worksheet,1,1,25,NULL);
lxw_image_options lio = {.x_offset = (pixel_width-new_w)/2, .y_offset = (pixel_height-new_h)/2};
worksheet_write_string(worksheet, 0, 0, "Ürün Adı", NULL);
worksheet_insert_image_buffer_opt(worksheet,0,1,data,sbytes.size(), &lio);
worksheet_write_string(worksheet, 1, 0, "Ürün Adı", NULL);
lio = {.x_offset = (pixel_width-scaled_img1.width())/2, .y_offset = (pixel_height-scaled_img1.height())/2};
worksheet_insert_image_buffer_opt(worksheet,1,1,data1,sbytes1.size(), &lio);
worksheet_write_string(worksheet, 2, 0, "Ürün Adı", NULL);
workbook_close(workbook);
return a.exec();
}
Now I get the following output:
The first image appears so small while the second one is slightly below the border line. Why is this?
How to insert the image at the center of the cell and within bounds of the cell width and height? Thanks.

You have to set the dpi to 96 (default for excel and other MS products) so that it is displayed correctly in the position at the correct size.
as QImage does have only set dot per meter i converted inch to meter then set dpm as follows:
QImage img
img.setDotsPerMeterX(3780);
img.setDotsPerMeterY(3780);
Now every thing is working as it should.

Related

How to render images to /dev/video0 using v4l2loopback?

I've been trying to render images to /dev/video. I can get something to sort of display but it's somewhat scrambled.
I first started off trying to render a normal RGB24 image (based off this example https://stackoverflow.com/a/44648382/3818491), but the result (below) was a scrambled image.
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <iostream>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <CImg.h>
#define VIDEO_OUT "/dev/video0" // V4L2 Loopack
#define WIDTH 1280
#define HEIGHT 720
int main() {
using namespace cimg_library;
CImg<uint8_t> canvas(WIDTH, HEIGHT, 1, 3);
const uint8_t red[] = {255, 0, 0};
const uint8_t purple[] = {255, 0, 255};
int fd;
if ((fd = open(VIDEO_OUT, O_RDWR)) == -1) {
std::cerr << "Unable to open video output!\n";
return 1;
}
struct v4l2_format vid_format;
vid_format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
if (ioctl(fd, VIDIOC_G_FMT, &vid_format) == -1) {
std::cerr << "Unable to get video format data. Errro: " << errno << '\n';
return 1;
}
size_t framesize = canvas.size();
int width = canvas.width(), height = canvas.height();
vid_format.fmt.pix.width = width;
vid_format.fmt.pix.height = height;
vid_format.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB24;
vid_format.fmt.pix.sizeimage = framesize;
vid_format.fmt.pix.field = V4L2_FIELD_NONE;
if (ioctl(fd, VIDIOC_S_FMT, &vid_format) == -1) {
std::cerr << "Unable to set video format! Errno: " << errno << '\n';
return 1;
}
std::cout << "Stream running!\n";
while (true) {
canvas.draw_plasma();
canvas.draw_rectangle(
100, 100, 100 + 100, 100 + 100, red, 1);
canvas.draw_text(5,5, "Hello World!", purple);
canvas.draw_text(5, 20, "Image freshly rendered with the CImg Library!", red);
write(fd, canvas.data(), framesize);
}
}
So I checked what (I think) /dev/video expects which seems to be YUV420P.
v4l2-ctl --list-formats-ext 130 ↵
ioctl: VIDIOC_ENUM_FMT
Type: Video Capture
[0]: 'YU12' (Planar YUV 4:2:0)
Size: Discrete 1280x720
Interval: Discrete 0.033s (30.000 fps)
So I attempted to convert the frame that format (using this code to quickly test).
Adjusting the spec to:
vid_format.fmt.pix.width = width;
vid_format.fmt.pix.height = height;
vid_format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420;
vid_format.fmt.pix.sizeimage = width*height*3/2; // size of yuv buffer
vid_format.fmt.pix.field = V4L2_FIELD_NONE;
That results in this (which seems to be from what I've gathered the structure of a yuv420 image but still rendered incorrectly).
What does /dev/video0 expect?
After a lot of hacking around, I've managed to generate a valid YUYV video/image to send to /dev/video0.
First I make a buffer to hold the frame:
// Allocate buffer for the YUUV frame
std::vector<uint8_t> buffer;
buffer.resize(vid_format.fmt.pix.sizeimage);
Then I write the current canvas to the buffer in YUYV format.
bool skip = true;
cimg_forXY(canvas, cx, cy) {
size_t row = cy * width * 2;
uint8_t r, g, b, y;
r = canvas(cx, cy, 0);
g = canvas(cx, cy, 1);
b = canvas(cx, cy, 2);
y = std::clamp<uint8_t>(r * .299000 + g * .587000 + b * .114000, 0, 255);
buffer[row + cx * 2] = y;
if (!skip) {
uint8_t u, v;
u = std::clamp<uint8_t>(r * -.168736 + g * -.331264 + b * .500000 + 128, 0, 255);
v = std::clamp<uint8_t>(r * .500000 + g * -.418688 + b * -.081312 + 128, 0, 255);
buffer[row + (cx - 1) * 2 + 1] = u;
buffer[row + (cx - 1) * 2 + 3] = v;
}
skip = !skip;
}
Note:
CImg has RGBtoYUV has an in-place RGB to YUV conversion, but for some reason calling this on a uint8_t canvas just zeros it.
It also has get_YUVtoRGB which (allocates and) returns a CImg<float> canvas, which I think you multiply each value by 255 to scale to a byte, however, whatever I tried that did not give the correct colour. Edit: I likely forgot the +128 bias (though I still prefer not reallocating for each frame)
My full code is here (if anyone wants to do something similar) https://gist.github.com/MacDue/36199c3f3ca04bd9fd40a1bc2067ef72

Compute QCryptographicHash of only the "core" QImage data (excluding metadata)

I have a bunch of "JPG" files that are different only for EXIF data.
I wanted to do a quick check (using the Qt Framework), trying to compute the Hash of the "core" image data (and not the file itself, which will include the metadata).
So far so good.
This is how I load an image and compute the Hash:
QImage img(R"(D:\Picture.jpg)");
auto data = QByteArray::fromRawData(reinterpret_cast<const char *>(img.constBits()), int(img.sizeInBytes()));
QCryptographicHash hash(QCryptographicHash::Sha256);
hash.addData(data);
qDebug() << hash.result().toHex();
I wanted to extend the same concept to files other than "JPG", so I've saved the original JPG file in different LOSSLESS formats (BMP, PNG, TIF), without altering the resolution.
I got a problem here. The Hash of the BMP, PNG, TIF images gives me the same result, but different from that of the same image in JPG.
If I would create a JPG file from a LOSSLES format I can understand the result.But the other way round???
Can someone help me understand where I'm wrong?
Give the following code I see that:
All QImage have the same bytes size
The QByteArray of the two JPG are identical
The QByteArray of the BMP, PNG, TIF are identical
The QByteArray of JPG and BMP (PNG and TIF too) are NOT identical
// JPG w/o EXIF data
QImage img1(R"(D:\Picture.jpg)");
auto data1 = QByteArray::fromRawData(reinterpret_cast<const char *>(img1.constBits()), int(img1.sizeInBytes()));
// JPG w/ EXIF data
QImage img2(R"(D:\Picture_EXIF.jpg)");
auto data2 = QByteArray::fromRawData(reinterpret_cast<const char *>(img2.constBits()), int(img2.sizeInBytes()));
// BMP
QImage img3(R"(D:\Picture.bmp)");
auto data3 = QByteArray::fromRawData(reinterpret_cast<const char *>(img3.constBits()), int(img3.sizeInBytes()));
// PNG w/o transparency
QImage img4(R"(D:\Picture.png)");
auto data4 = QByteArray::fromRawData(reinterpret_cast<const char *>(img4.constBits()), int(img4.sizeInBytes()));
// TIF (lossles)
QImage img5(R"(D:\Picture.tif)");
auto data5 = QByteArray::fromRawData(reinterpret_cast<const char *>(img5.constBits()), int(img5.sizeInBytes()));
qDebug() << img1.sizeInBytes(); // 23918592
qDebug() << img2.sizeInBytes(); // 23918592
qDebug() << img3.sizeInBytes(); // 23918592
qDebug() << img4.sizeInBytes(); // 23918592
qDebug() << img5.sizeInBytes(); // 23918592
qDebug() << (data1 == data2); // True
qDebug() << (data1 == data3); // False
qDebug() << (data3 == data4); // True
qDebug() << (data3 == data5); // True
qDebug() << img1.format(); // 4 = QImage::Format_RGB32
qDebug() << img2.format(); // 4 = QImage::Format_RGB32
qDebug() << img3.format(); // 4 = QImage::Format_RGB32
qDebug() << img4.format(); // 5 = QImage::Format_ARGB32
qDebug() << img5.format(); // 6 = QImage::Format_ARGB32_Premultiplied
QCryptographicHash hash(QCryptographicHash::Sha256);
hash.reset(); hash.addData(data1); qDebug() << hash.result().toHex(); // c37143639914056add1f90be4bfe780e14500d24f1d3484a087fc1943508157f
hash.reset(); hash.addData(data2); qDebug() << hash.result().toHex(); // c37143639914056add1f90be4bfe780e14500d24f1d3484a087fc1943508157f
hash.reset(); hash.addData(data3); qDebug() << hash.result().toHex(); // 0149c60b883df67ba002d791a1362dbd02ccab09241864341483a16ec0af635d
hash.reset(); hash.addData(data4); qDebug() << hash.result().toHex(); // 0149c60b883df67ba002d791a1362dbd02ccab09241864341483a16ec0af635d
hash.reset(); hash.addData(data5); qDebug() << hash.result().toHex(); // 0149c60b883df67ba002d791a1362dbd02ccab09241864341483a16ec0af635d
Final conclusion
As in subsequent tests I realized that the problem is neither Qt nor my (last) implementation of the code (thanks #Scheff).
The BMP, PNG and TIF are actually different from the original JPG file!
The files BMP, PNG and TIF were created by opening the original JPG file in Windows Paint and saving it in those lossless formats. So Windows Paint fails somehow in the reading (or) saving steps.
The commercial software Duplicate Cleaner fails as well because it reported the JPG file to be 100% identical to the BMP, PNG, TIF version.
Preface:
I consider
data1.append(reinterpret_cast<const char *>(img1.constBits()));
as wrong way to fill a QByteArray with data which doesn't store a C string.
QByteArray::append(const char*) is good to copy C strings in a QByteArray. It copies data until a 0 byte (a 0-terminator) is found. 0 bytes may be anywhere in the raw data of an image or nowhere. In the first case, too less data is copied, in the latter case, out-of-range data is considered. Both is unintended.
Btw. it's even not necessary to copy the image data (which might be of significant size).
I made a sample to compare the raw data of two QImages directly, including a pre-check whether size and format does match.
My sample testQImageRawCmp.cc
#include <QtWidgets>
bool equalImgData(const QImage &qImg1, const QImage &qImg2, int eps = 0)
{
// test properties
#define TEST_PROP(PROP) \
do if (qImg1.PROP != qImg2.PROP) { \
qDebug() << "qImg1."#PROP" != qImg2."#PROP; \
return false; \
} while(false)
TEST_PROP(width());
TEST_PROP(height());
TEST_PROP(format());
#undef TEST_PROP
// test raw data
const uchar *const data1 = qImg1.bits();
const uchar *const data2 = qImg2.bits();
const int bytesPerLine1 = qImg1.bytesPerLine();
const int bytesPerLine2 = qImg2.bytesPerLine();
const int nBits = qImg1.depth() * qImg1.width();
const int nBytes = (nBits + 7) / 8;
assert(nBytes <= bytesPerLine1);
assert(nBytes <= bytesPerLine2);
for (int y = 0; y < qImg1.height(); ++y) {
const uchar *row1 = data1 + y * bytesPerLine1;
const uchar *row2 = data2 + y * bytesPerLine2;
for (int x = 0; x < nBytes; ++x) {
if (abs(row2[x] - row1[x]) > eps) {
qDebug() << "Row" << y << "byte" << x << "difference:" << (row2[x] - row1[x]);
return false;
}
}
}
return true;
}
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
// load sample data
QImage img1("Picture.jpg");
QImage img2("Picture.bmp");
#if 0 // U.B.
// Juttas test:
QByteArray data1; data1.append(reinterpret_cast<const char *>(img1.constBits()));
QByteArray data2; data2.append(reinterpret_cast<const char *>(img2.constBits()));
#endif // 0
// My test:
if (!equalImgData(img1, img2, 3)) {
qDebug() << "Images not equal!";
} else {
qDebug() << "Images equal.";
}
}
I tested this program with the sample data provided by OP:
Picture.jpg
Picture.bmp
and got the following output:
Qt Version: 5.13.0
Row 0 byte 60 difference: 1
Images not equal!
I must admit, the first version of this code just reported the unequality.
Then, I tried to make my own counter-sample and converted the Picture.bmp to Picture.bmp.jpg in GIMP (with 100 % quality setting which I would've considered as loss-less). This resulted in a difference in Row 0 byte 0. Ooops!
Then, I became curious and modified the code to see how much different the images are.
A difference of 1 in a red, green, or blue value of a pixel is not much. I doubt that this is even visible for the average human.
Hence, I modified the code (into the exposed version) to tolerate some kind of difference.
With an eps of 3:
if (equalImgData(img1, img2, 3)) {
the images were considered as equal.

how to create a graphics object using an Atom text editor

I am having a hard time determining a way to include graphics.h file in my compiler. All information I have came across is for IDE such as CodeBlocks. I would like to be able include graphics file for use without facing any problems. My questions are:
Can you use a text editor like Atom to create a graphics object?
If so what steps should be taken in order to accomplish that?
There are lot of graphics formats available with varying capabilities.
First distinction I would make is:
raster graphics vs. vector graphics
Raster graphics (storing the image pixel by pixel) are more often binary encoded as the amount of data is usally directly proportional to size of image. However some of them are textual encoded or can be textual as well as binary encoded.
Examples are:
Portable anymap
X Pixmap
Although these file formats are a little bit exotic, it is not difficult to find software which supports them. E.g. GIMP supports both out of the box (and even on Windows). Btw. they are that simple that it is not too complicated to write loader and writer by yourself.
A simple reader and writer for PPM (the color version of Portable anymap) can be found in my answer to SO: Convolution for Edge Detection in C.
Vector graphics (store graphics primitives which build the image) are more often textual encoded. As vector graphics can be "loss-less" scaled to any image size by simply applying a scaling factor to all coordinates, file size and destination image size are not directly related. Thus, vector graphics are the preferrable format for drawings especially if they are needed in multiple target resolutions.
For this, I would exclusively recommend:
Scalable Vector Graphics
which is (hopefully) the upcoming standard for scalable graphics in Web contents. Qt does provide (limited) support for SVG and thus, it is my preferred option for resolution independent icons.
A different (but maybe related) option is to embed graphics in source code. This can be done with rather any format if your image loader library provides image loading from memory (as well as from file). (All I know does this.)
Thus, the problem can be reduced to: How to embed a large chunk of (ASCII or binary) data as constant in C/C++ source code? which is IMHO trivial to solve.
I did this in my answer for SO: Paint a rect on qglwidget at specifit times.
Update:
As I noticed that the linked sample for PPM (as well as another for PBM) read actually the binary format, I implemented a sample application which demonstrates usage of ASCII PPM.
I believe that XPM is better suitable for the specific requirement to be editable in a text editor. Thus, I considered this in my sample also.
As the question doesn't mention what specific internal image format is desired nor in what API it shall be usable, I choosed Qt which
is something I'm familiar with
provides a QImage which is used as destination for image import
needs only a few lines of code for visual output of result.
Source code test-QShowPPM-XPM.cc:
// standard C++ header:
#include <cassert>
#include <iostream>
#include <string>
#include <sstream>
// Qt header:
#include <QtWidgets>
// sample image in ASCII PPM format
// (taken from https://en.wikipedia.org/wiki/Netpbm_format)
const char ppmData[] =
"P3\n"
"3 2\n"
"255\n"
"255 0 0 0 255 0 0 0 255\n"
"255 255 0 255 255 255 0 0 0\n";
// sample image in XPM3 format
/* XPM */
const char *xpmData[] = {
// w, h, nC, cPP
"16 16 5 1",
// colors
" c #ffffff",
"# c #000000",
"g c #ffff00",
"r c #ff0000",
"b c #0000ff",
// pixels
" ## ",
" ###gg### ",
" #gggggggg# ",
" #gggggggggg# ",
" #ggbbggggbbgg# ",
" #ggbbggggbbgg# ",
" #gggggggggggg# ",
"#gggggggggggggg#",
"#ggrrggggggrrgg#",
" #ggrrrrrrrrgg# ",
" #ggggrrrrgggg# ",
" #gggggggggggg# ",
" #gggggggggg# ",
" #gggggggg# ",
" ###gg### ",
" ## "
};
// Simplified PPM ASCII Reader (no support of comments)
inline int clamp(int value, int min, int max)
{
return value < min ? min : value > max ? max : value;
}
inline int scale(int value, int maxOld, int maxNew)
{
return value * maxNew / maxOld;
}
QImage readPPM(std::istream &in)
{
std::string header;
std::getline(in, header);
if (header != "P3") throw "ERROR! Not a PPM ASCII file.";
int w = 0, h = 0, max = 255; // width, height, bits per component
if (!(in >> w >> h >> max)) throw "ERROR! Premature end of file.";
if (max <= 0 || max > 255) throw "ERROR! Invalid format.";
QImage qImg(w, h, QImage::Format_RGB32);
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
int r, g, b;
if (!(in >> r >> g >> b)) throw "ERROR! Premature end of file.";
qImg.setPixel(x, y,
scale(clamp(r, 0, 255), max, 255) << 16
| scale(clamp(g, 0, 255), max, 255) << 8
| scale(clamp(b, 0, 255), max, 255));
}
}
return qImg;
}
// Simplified XPM Reader (implements sub-set of XPM3)
char getChar(const char *&p)
{
if (!*p) throw "ERROR! Premature end of file.";
return *p++;
}
std::string getString(const char *&p)
{
std::string str;
while (*p && !isspace(*p)) str += *p++;
return str;
}
void skipWS(const char *&p)
{
while (*p && isspace(*p)) ++p;
}
QImage readXPM(const char **xpmData)
{
int w = 0, h = 0; // width, height
int nC = 0, cPP = 1; // number of colors, chars per pixel
{ std::istringstream in(*xpmData);
if (!(in >> w >> h >> nC >> cPP)) throw "ERROR! Premature end of file.";
++xpmData;
}
std::map<std::string, std::string> colTbl;
for (int i = nC; i--; ++xpmData) {
const char *p = *xpmData;
std::string chr;
for (int j = cPP; j--;) chr += getChar(p);
skipWS(p);
if (getChar(p) != 'c') throw "ERROR! Format not supported.";
skipWS(p);
colTbl[chr] = getString(p);
}
QImage qImg(w, h, QImage::Format_RGB32);
for (int y = 0; y < h; ++y, ++xpmData) {
const char *p = *xpmData;
for (int x = 0; x < w; ++x) {
std::string pixel;
for (int j = cPP; j--;) pixel += getChar(p);
qImg.setPixelColor(x, y, QColor(colTbl[pixel].c_str()));
}
}
return qImg;
}
// a customized QLabel to handle scaling
class LabelImage: public QLabel {
private:
QPixmap _qPixmap, _qPixmapScaled;
public:
LabelImage();
LabelImage(const QPixmap &qPixmap): LabelImage()
{
setPixmap(qPixmap);
}
LabelImage(const QImage &qImg): LabelImage(QPixmap::fromImage(qImg))
{ }
void setPixmap(const QPixmap &qPixmap) { setPixmap(qPixmap, size()); }
protected:
virtual void resizeEvent(QResizeEvent *pQEvent);
private:
void setPixmap(const QPixmap &qPixmap, const QSize &size);
};
// main function
int main(int argc, char **argv)
{
qDebug() << QT_VERSION_STR;
// main application
#undef qApp // undef macro qApp out of the way
QApplication qApp(argc, argv);
// setup GUI
QMainWindow qWin;
QGroupBox qBox;
QGridLayout qGrid;
LabelImage qLblImgPPM(readPPM(std::istringstream(ppmData)));
qGrid.addWidget(&qLblImgPPM, 0, 0, Qt::AlignCenter);
LabelImage qLblImgXPM(readXPM(xpmData));
qGrid.addWidget(&qLblImgXPM, 1, 0, Qt::AlignCenter);
qBox.setLayout(&qGrid);
qWin.setCentralWidget(&qBox);
qWin.show();
// run application
return qApp.exec();
}
// implementation of LabelImage
LabelImage::LabelImage(): QLabel()
{
setFrameStyle(Raised | Box);
setAlignment(Qt::AlignCenter);
//setMinimumSize(QSize(1, 1)); // seems to be not necessary
setSizePolicy(QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored));
}
void LabelImage::resizeEvent(QResizeEvent *pQEvent)
{
QLabel::resizeEvent(pQEvent);
setPixmap(_qPixmap, pQEvent->size());
}
void LabelImage::setPixmap(const QPixmap &qPixmap, const QSize &size)
{
_qPixmap = qPixmap;
_qPixmapScaled = _qPixmap.scaled(size, Qt::KeepAspectRatio);
QLabel::setPixmap(_qPixmapScaled);
}
This has been compiled in VS2013 and tested in Windows 10 (64 bit):

Rendering a section of a non-smooth QImage using QSGImageNode

I'm trying to render individual tiles from a tileset. For example, I want to display the grey tile in the tileset below:
In the real use case, these would be e.g. water, grass, etc. tiles in a game. There are some requirements for rendering these tiles:
They are 32x32 pixels and will be rendered fullscreen, so performance is important.
They should not be smoothed when scaled.
None of the built-in Qt Quick types meet these requirements (rendering a section of an image that's not smoothed), as far as I can tell. I've tried QQuickPaintedItem with various QPainter render hints (such as SmoothPixmapTransform set to false) without success; the image is "blurry" when upscaled. AnimatedSprite supports rendering sections of an image, but has no API to disable smoothing.
My idea was to implement a custom QQuickItem using the scene graph API.
main.cpp:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickItem>
#include <QQuickWindow>
#include <QSGImageNode>
static QImage image;
static const int tileSize = 32;
static const int tilesetSize = 8;
class Tile : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(int index READ index WRITE setIndex NOTIFY indexChanged)
public:
Tile() :
mIndex(-1) {
setWidth(tileSize);
setHeight(tileSize);
setFlag(QQuickItem::ItemHasContents);
}
QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
{
if (!oldNode) {
oldNode = window()->createImageNode();
}
if (mIndex == -1)
return oldNode;
if (image.isNull()) {
image = QImage("C:/tileset.png");
if (image.isNull())
return oldNode;
}
QSGTexture *texture = window()->createTextureFromImage(image);
qDebug() << "textureSize:" << texture->textureSize();
if (!texture)
return oldNode;
QSGImageNode *imageNode = static_cast<QSGImageNode*>(oldNode);
// imageNode->setOwnsTexture(true);
imageNode->setTexture(texture);
qDebug() << "source rect:" << (mIndex % tileSize) * tileSize << (mIndex / tileSize) * tileSize << tileSize << tileSize;
imageNode->setSourceRect((mIndex % tileSize) * tileSize, (mIndex / tileSize) * tileSize, tileSize, tileSize);
return oldNode;
}
int index() const {
return mIndex;
}
void setIndex(int index) {
if (index == mIndex)
return;
mIndex = index;
emit indexChanged();
}
signals:
void indexChanged();
private:
int mIndex;
};
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
qmlRegisterType<Tile>("App", 1, 0, "Tile");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
#include "main.moc"
main.qml:
import QtQuick 2.9
import QtQuick.Controls 2.2
import App 1.0
ApplicationWindow {
id: window
width: 800
height: 800
visible: true
Slider {
id: slider
from: 1
to: 10
}
Tile {
scale: slider.value
index: 1
anchors.centerIn: parent
Rectangle {
anchors.fill: parent
color: "transparent"
border.color: "darkorange"
}
}
}
The output from this application looks fine, but nothing is rendered within the rectangle:
textureSize: QSize(256, 256)
source rect: 32 0 32 32
Judging from the minimal docs, my implementation (in terms of how I create nodes) seems OK. Where am I going wrong?
A year late to the party, but I was running into the same problem as you. For the sake of anyone else who is trying to subclass a QQuickItem and has come across this thread, there's a little nugget that's in the documentation in regards to updatePaintNode:
The function is called as a result of QQuickItem::update(),
if the user has set the QQuickItem::ItemHasContents flag on the item.
When I set that flag, everything rendered.
And I considered myself a detail-oriented person...
EDIT:
After the OP pointed out they had already set the ItemHasContents flag, I looked at the code again and saw that while the OP had set the sourceRect on the node, the OP hadn't set the rect of the node, and that indeed was the problem the OP was running into.
I ended up going with a friend's idea of using QQuickImageProvider:
tileimageprovider.h:
#ifndef TILEIMAGEPROVIDER_H
#define TILEIMAGEPROVIDER_H
#include <QQuickImageProvider>
#include <QHash>
#include <QString>
#include <QImage>
class TileImageProvider : public QQuickImageProvider
{
public:
TileImageProvider();
QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override;
private:
QHash<QString, QImage> mTiles;
};
#endif // TILEIMAGEPROVIDER_H
tileimageprovider.cpp:
#include "tileimageprovider.h"
#include <QImage>
#include <QDebug>
TileImageProvider::TileImageProvider() :
QQuickImageProvider(QQmlImageProviderBase::Image)
{
QImage tilesetImage(":/sprites/tiles/tileset.png");
if (tilesetImage.isNull()) {
qWarning() << "Failed to load tileset image";
return;
}
int index = 0;
for (int row = 0; row < 8; ++row) {
for (int col = 0; col < 8; ++col) {
int sourceX = col * 32;
int sourceY = row * 32;
QImage subTile = tilesetImage.copy(sourceX, sourceY, 32, 32);
if (tilesetImage.isNull()) {
qWarning() << "Tile image at" << sourceX << sourceY << "is null";
return;
}
mTiles.insert(QString::number(index++), subTile);
}
}
}
QImage TileImageProvider::requestImage(const QString &id, QSize *size, const QSize &)
{
Q_ASSERT(mTiles.find(id) != mTiles.end());
*size = QSize(32, 32);
return mTiles.value(id);
}
I then create tile instances from the following Component:
Image {
width: 32
height: 32
smooth: false
asynchronous: true
source: "image://tile/" + index
property int index
}
There's also Tiled's tile rendering code, which uses scenegraph nodes and is likely more efficient.
It does seem like you can have it both easier and more efficient.
Instead of implementing a custom QQuickItem you can just use a trivial ShaderEffect. You can pass an offset and control the sampling.
Additionally, you would only need one single black and white plus, and you can have the color dynamic by passing it as a parameter to the shader.
Lastly, doing an atlas yourself is likely redundant, as the scenegraph will likely put small textures in atlases anyway. And of course, there is the advantage of not having to write a single line of C++, while still getting an efficient and flexible solution.

Fetch text from unnamed QGraphicsTextItem

A friend of mine and I are currently trying to make a game in C++ using Qt. Our current problem is that we need to fetch text from a QGraphicsTextItem on a button mousePressEvent. In the game menu it is possible to choose how many players there are, therefore we've placed a QGraphicsTextItem in a for-loop to make it possible for all the users to type in their names. Because of the for-loop, we don't have names for every single text item object so we can store the names. We've managed to store all the memory addresses to the objects using QMap, but we don't know how to get the text of of this. We don't even know if this is the best way to do it.
GameInfo.h
class GameInfo {
public:
GameInfo();
int players;
QStringList names = (QStringList() // Default player names. This array should be overwritten by the custom names
<< "Player 1"
<< "Player 2"
<< "Player 3"
<< "Player 4"
<< "Player 5"
<< "Player 6"
<< "Player 7");
QMap<int, QGraphicsTextItem**> textBoxMap; // This is where we store all the addresses
};
Game.cpp
QGraphicsRectItem * overviewBox = new QGraphicsRectItem();
overviewBox->setRect(0, 0, 782, 686);
scene->addItem(overviewBox);
int faceNo = 0;
// Create the player selection section
for(int i = 1; i <= players; i++) { // "players" is defined another place in the code, and is an integer between 1 and 6
Container * selContainer = new Container();
selContainer->Selection(i, faceNo);
selContainer->setPos(50, 70 + 110 * (i - 1));
scene->addItem(selContainer);
Container * ovContainer = new Container(overviewBox);
ovContainer->Overview(i, faceNo);
ovContainer->setPos(0, 0 + 110 * (i - 1));
info->textBoxMap.insert(i, &selContainer->textBox->playerText); // This is where we save the addresses
}
Selection.cpp
extern Game * game;
Container::Container(QGraphicsItem * parent): QGraphicsRectItem(parent) {
}
void Container::Selection(int nPlayers, int sPiceNo, QGraphicsItem *parent) {
QString numName = QString::number(nPlayers);
setRect(0, 0, 672, 110);
this->setPen(Qt::NoPen); // Removes border
int posY = this->rect().height() / 2;
QSignalMapper * signalMapper = new QSignalMapper(this);
arrowL = new Arrow(0, posY - 32, 0, this);
piece = new Piece(sPiceNo, 96, posY - 32, 1, 1, this);
arrowR = new Arrow(192, posY - 32, 1, this);
textBox = new TextBox(game->info->names[nPlayers - 1], true, this);
textBox->setPos(288, posY - 32);
lockBtn = new Button("Lock", 96, 32, this);
connect(lockBtn, SIGNAL(clicked()), signalMapper, SLOT(map()));
signalMapper->setMapping(lockBtn, nPlayers);
connect(signalMapper, SIGNAL(mapped(int)), this, SLOT(lock(int)));
lockBtn->setPos(640, posY - 16);
}
void Container::Overview(int ovPlayers, int ovPiceNo, QGraphicsItem * parent) {
// Some code...
}
void Container::lock(int nPlayer) {
qDebug() << game->info->textBoxMap[nPlayer];
qDebug() << game->info->names[nPlayer - 1];
game->info->names[nPlayer - 1] = **game->info->textBoxMap[nPlayer].toPlainText(); // This line causes an error
}
The error that occurs because of the last line looks like this:
error: no match for 'operator=' (operand types are 'QString' and 'QGraphicsTextItem')
game->info->names[nPlayer - 1] = **game->info->textBoxMap[nPlayer];
^
TextBox.cpp
TextBox::TextBox(QString text, bool editable, QGraphicsItem * parent): QGraphicsRectItem(parent) {
this->editable = editable;
// Draw the textbox
setRect(0, 0, 320, 64);
if(!editable) {
this->setPen(Qt::NoPen); // Removes border
}
else if(editable) {
QBrush brush;
brush.setStyle(Qt::SolidPattern);
brush.setColor(QColor(255, 255, 255, 255));
setBrush(brush);
}
// Draw the text
playerText = new QGraphicsTextItem(text, this);
int fontId = QFontDatabase::addApplicationFont(":/fonts/built_titling_bd.ttf");
QString family = QFontDatabase::applicationFontFamilies(fontId).at(0);
QFont built(family, 25);
playerText->setFont(built);
int xPos = 0;
int yPos = rect().height() / 2 - playerText->boundingRect().height() / 2;
playerText->setPos(xPos,yPos);
}
My question is how do i fetch the text from the QGraphicsTextItem?
You should try to learn a bit more about C++ before trying to develop a game imo. (Having public variables is against OO's and C++ paradigm)
But here is what you are looking for:
http://doc.qt.io/qt-5/qgraphicstextitem.html#toPlainText
EDIT:
If you are not able to debug some line of code, I could only recommend to try to seperate your code in order to have a minimum of call in a single line. I haven't try the code bellow, but that how you should try to debug your code:
void Container::lock(int nPlayer)
{
qDebug() << game->info->textBoxMap[nPlayer];
qDebug() << game->info->names[nPlayer - 1];
QGraphicsTextItem **value = game->info->textBoxMap.value(nPlayer, nullptr);
game->info->names[nPlayer - 1] =(*value)->toPlainText();
}