C++ how can I simplify this if else statement? - c++

I would like to know how I could simplify a statement like the one below.
I have similar code everywhere, and would like to clear it up.
if(isActive)
{
if(columnId == 4)
g.drawText(active[row].value, 2, 0, width, height, Justification::centredLeft, true);
}
else
{
if(columnId == 4)
g.drawText(inactive[row].value, 2, 0, width, height, Justification::centredLeft, true);
}
isActive, as you can imagine, is a bool value.

At first glance, it's most apparent that this code only does anything if columnId == 4.
if(columnId == 4)
{
if(isActive)
{
g.drawText(active[row].value, 2, 0, width, height, Justification::centredLeft, true);
}
else
{
g.drawText(inactive[row].value, 2, 0, width, height, Justification::centredLeft, true);
}
}
At second glance, those two bulky lines are almost the same.
if(columnId == 4)
{
auto & text = isActive ? active : inactive;
g.drawText(text[row].value, 2, 0, width, height, Justification::centredLeft, true);
}
Note, also, the valid comment from #eerorika. ⬇️ I couldn't say it better than they did.

if (columnId != 4)
; // do nothing
else if (isActive)
. . .
else
. . .

A lambda could be used to simplify the original code structure, by reducing the amount of duplicated text.
auto drawText = [&](auto &table) {
g.drawText(table[row].value, 2, 0, width, height,
Justification::centeredLeft, true);
};
if(isActive)
{
if(columnId == 4) drawText(active);
}
else
{
if(columnId == 4) drawText(inactive);
}

Related

Repositioning of MFCToolBars

I am using Visual Studio 2019, I created an MDI application.
I have placed 4 toolbars in a row in CMainFrame (derived from CMDIFrameWndEx).
Depending on the active view I hide one toolbar, and thus it’s possible have a gap
between two toolbars.
How can I close the gap automatically by attaching the right toolbar to the left?
Here is my code:
LRESULT CMainFrame::OnActivateViewSelect(WPARAM w, LPARAM l)
{
if (m_bViewInit)
{
auto iViewSelect = _S32(w);
if (iViewSelect != m_lastpane)
{
m_lastpane = iViewSelect;
ShowPane(&m_wndToolBar[0], iViewSelect == 0, FALSE, iViewSelect == 0);
ShowPane(&m_wndToolBar[1], iViewSelect == 1, FALSE, iViewSelect == 1);
ShowPane(&m_wndToolBar[2], iViewSelect == 2, FALSE, iViewSelect == 2);
ShowPane(&m_wndToolBar[3], iViewSelect == 3, FALSE, iViewSelect == 3);
RecalcLayout();
}
}
return 0L;
}

Convert Gdiplus::Region to ID2D1Geometry* for clipping

I am trying to migrate my graphics interface project from Gdiplus to Direct2D.
Currently, I have a code that calculates clipping area for an rendering object:
Graphics g(hdc);
Region regC = Rect(x, y, cx + padding[2] + padding[0], cy + padding[3] + padding[1]);
RecursRegPos(this->parent, &regC);
RecursRegClip(this->parent, &regC);
g.setClip(g);
...
inline void RecursRegClip(Object *parent, Region* reg)
{
if (parent == CARD_PARENT)
return;
if (parent->overflow != OVISIBLE)
{
Rect regC(parent->getAbsoluteX(), parent->getAbsoluteY(), parent->getCx(), parent->getCy()); // Implementation of these function is not necceassary
GraphicsPath path;
path.Reset();
GetRoundRectPath(&path, regC, parent->borderRadius[0]);
// source https://stackoverflow.com/a/71431813/15220214, but if diameter is zero, just plain rect is returned
reg->Intersect(&path);
}
RecursRegClip(parent->parent, reg);
}
inline void RecursRegPos(Object* parent, Rect* reg)
{
if (parent == CARD_PARENT)
return;
reg->X += parent->getX() + parent->padding[0];
reg->Y += parent->getY() + parent->padding[1];
if (parent->overflow == OSCROLL || parent->overflow == OSCROLLH)
{
reg->X -= parent->scrollLeft;
reg->Y -= parent->scrollTop;
}
RecursRegPos(parent->parent, reg);
}
And now I need to convert it to Direct2D. As You may notice, there is no need to create Graphics object to get complete calculated clipping region, so I it would be cool if there is way to just convert Region to ID2D1Geometry*, that, as far, as I understand from msdn article need to create clipping layer.
Also, there is probably way to convert existing code (RecursRegClip, RecursRegPos) to Direct2D, but I am facing problems, because I need to work with path, but current functions get region as an argument.
Update 1
There is Region::GetData method that returns, as I understand array of points, so maybe there is possibility to define either ID2D1PathGeometry or ID2D1GeometrySink by points?
Update 2
Oh, maybe
ID2D1GeometrySink::AddLines(const D2D1_POINT_2F *points, UINT32 pointsCount)
is what do I need?
Unfortunately, GetData of region based on just (0,0,4,4) rectangle returns 36 mystique values:
Region reg(Rect(0, 0, 4, 4));
auto so = reg.GetDataSize();
BYTE* are = new BYTE[so];
UINT fi = 0;
reg.GetData(are, so, &fi);
wchar_t ou[1024]=L"\0";
for (int i = 0; i < fi; i++)
{
wchar_t buf[10] = L"";
_itow_s(are[i], buf, 10, 10);
wcscat_s(ou, 1024, buf);
wcscat_s(ou, 1024, L", ");
}
// ou - 28, 0, 0, 0, 188, 90, 187, 128, 2, 16, 192, 219, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 64, 0, 0, 128, 64,
I rewrote the solution completely, it seems to be working:
// zclip is ID2D1PathGeometry*
inline void Render(ID2D1HwndRenderTarget *target)
{
ID2D1RoundedRectangleGeometry* mask = nullptr;
ID2D1Layer* clip = nullptr;
if(ONE_OF_PARENTS_CLIPS_THIS || THIS_HAS_BORDER_RADIUS)
{
Region reg = Rect(x, y, cx + padding[2] + padding[0], cy + padding[3] + padding[1]);
RecursRegPos(this->parent, &reg);
D2D1_ROUNDED_RECT maskRect;
maskRect.rect.left = reg.X;
maskRect.rect.top = reg.Y;
maskRect.rect.right = reg.X + reg.Width;
maskRect.rect.bottom = reg.Y + reg.Height;
maskRect.radiusX = this->borderRadius[0];
maskRect.radiusY = this->borderRadius[1];
factory->CreateRoundedRectangleGeometry(maskRect, &mask);
RecursGeoClip(this->parent, mask);
target->CreateLayer(NULL, &clip);
if(zclip)
target->PushLayer(D2D1::LayerParameters(D2D1::InfiniteRect(), zclip), clip);
else
target->PushLayer(D2D1::LayerParameters(D2D1::InfiniteRect(), mask), clip);
SafeRelease(&mask);
}
// Draw stuff here
if (clip)
{
target->PopLayer();
SafeRelease(&clip);
SafeRelease(&mask);
SafeRelease(&zclip);
}
}
...
inline void RecursGeoClip(Object* parent, ID2D1Geometry* geo)
{
if (parent == CARD_PARENT)
return;
ID2D1RoundedRectangleGeometry* maskParent = nullptr;
if (parent->overflow != OVISIBLE)
{
Rect regC(parent->getAbsoluteX(), parent->getAbsoluteY(), parent->getCx(), parent->getCy());
ID2D1GeometrySink* sink = nullptr;
ID2D1PathGeometry* path = nullptr;
SafeRelease(&path);
factory->CreatePathGeometry(&path);
D2D1_ROUNDED_RECT maskRect;
maskRect.rect.left = regC.X;
maskRect.rect.top = regC.Y;
maskRect.rect.right = regC.X + regC.Width;
maskRect.rect.bottom = regC.Y + regC.Height;
maskRect.radiusX = parent->borderRadius[0];
maskRect.radiusY = parent->borderRadius[1];
path->Open(&sink);
factory->CreateRoundedRectangleGeometry(maskRect, &maskParent);
geo->CombineWithGeometry(maskParent, D2D1_COMBINE_MODE_INTERSECT, NULL, sink);
sink->Close();
SafeRelease(&sink);
SafeRelease(&this->zclip);
this->zclip = path;
RecursGeoClip(parent->parent, this->zclip);
}
else
RecursGeoClip(parent->parent, geo);
SafeRelease(&maskParent);
}
Now I can enjoy drawing one image and two rectangles in more than 60 fps, instead of 27 (in case of 200x200 image size, higher size - lower fps) with Gdi+ -_- :

Posting multiple forms in ncurses: only last form is initially visible

I have a few sets of forms within my ncurses program. Each field has A_UNDERLINE enabled but I noticed that only the last form posted will show underlines until I navigate to it; the others are simply blank space.
I would like to ensure all the fields appear with underlines as soon as I refresh my pad.
Eventually, I intend to allow users to add and remove fields within a form and will compile all field contents within a form together for analysis. Therefore putting all the fields into one form is possible but not ideal at all.
Since I'm new to curses, I'm a little lost as to what I should even be trying. I've tried calling form_driver(form, REQ_END_LINE); for each form prior to looping on input to no avail. As well as form_driver(form, REQ_NEXT_FIELD); since that seemed to work inside the input loop.
I'm clearly missing something but I'm not sure if I haven't correctly initialized my fields/forms or if I'm simply misunderstanding how these work.
I've created a simplified version of the issue below:
#include <ncurses.h>
#include <form.h>
int main()
{
WINDOW *pad;
FIELD *field[3];
FORM *my_form;
FIELD *field_two[3];
FORM *form_two;
int ch;
initscr();
cbreak();
noecho();
keypad(stdscr, TRUE);
pad = newpad(LINES,COLS);
keypad(pad, TRUE);
field[0] = new_field(1, 10, 4, 18, 0, 0);
field[1] = new_field(1, 10, 6, 18, 0, 0);
field[2] = NULL;
field_two[0] = new_field(1, 10, 8, 18, 0, 0);
field_two[1] = new_field(1, 10, 10, 18, 0, 0);
field_two[2] = NULL;
set_field_back(field[0], A_UNDERLINE);
set_field_back(field[1], A_UNDERLINE);
set_field_back(field_two[0], A_UNDERLINE);
set_field_back(field_two[1], A_UNDERLINE);
wclear(pad);
my_form = new_form(field);
set_form_sub(my_form, pad);
post_form(my_form);
form_two = new_form(field_two);
set_form_sub(form_two, pad);
post_form(form_two);
mvwprintw(pad, 1, 1, "Some text");
mvwprintw(pad, 4, 10, "Value 1:");
mvwprintw(pad, 6, 10, "Value 2:");
mvwprintw(pad, 8, 10, "Value 3:");
mvwprintw(pad, 10, 10, "Value 4:");
prefresh(pad, 0, 0, 0, 0, LINES, COLS);
bool form1 = true;
while((ch = getch()) != KEY_F(1))
{
switch(ch)
{ case KEY_DOWN:
if (form1) {
form_driver(my_form, REQ_NEXT_FIELD);
form_driver(my_form, REQ_END_LINE);
} else {
form_driver(form_two, REQ_NEXT_FIELD);
form_driver(form_two, REQ_END_LINE);
}
break;
case KEY_UP:
if (form1) {
form_driver(my_form, REQ_PREV_FIELD);
form_driver(my_form, REQ_END_LINE);
} else {
form_driver(form_two, REQ_PREV_FIELD);
form_driver(form_two, REQ_END_LINE);
}
break;
case KEY_RIGHT:
form1 = false;
break;
case KEY_LEFT:
form1 = true;
break;
default:
if (form1) {
form_driver(my_form, ch);
} else {
form_driver(form_two, ch);
}
break;
}
prefresh(pad, 0, 0, 0, 0, LINES, COLS);
}
unpost_form(my_form);
free_form(my_form);
free_field(field[0]);
free_field(field[1]);
unpost_form(form_two);
free_form(form_two);
free_field(field_two[0]);
free_field(field_two[1]);
endwin();
return 0;
}
When the above is executed, I have a blank screen until I press a key (if you feel like pointing out why that is I'd like to hear but that's not the point of this question). After that, I see the fields for Value 3 and Value 4 have underlines but Value 1 and Value 2 do not until I start typing in them.
My goal here is to have underlines for fields on the same lines as Value 1 and Value 2 without any additional user input.
I was able to accomplish my goal of all fields being underlined by adding the following code immediately under post_form(form_two);
set_field_back(field[0], 0);
set_field_back(field[1], 0);
set_field_back(field[0], A_UNDERLINE);
set_field_back(field[1], A_UNDERLINE);

Image processing: pointer becomes nullptr without resetting it

I am working on a project which recovers an image line by line and has to recolor any objects that are inside the image. Our image is gathered in real time and we receive a 1D byte[] array on each call. Placing each 1D array on top of each other reconstructs the 2D image. This program has a buffer of 300 arrays which is large enough to display each object.
The data inside the array is a numeric value corresponding to a color. Objects often have several colors and this code has to recolor each object with the most present color.
Initially, this code was written in C#, but I had several performance issues so this program was adapted to a CLI/CLR project with C++ to have access to pointers which would hugely improve speed.
For each pixel in the array, I instanciate a Product class which contains data about the current pixel. Each Product class contains a pointer to a Info class containing data about the current object. The program looks at previous adjacent pixels and determines if the pixel belongs to the same object. If so, the Info pointer in the Product class of the current pixel will point to the Info class of the previous pixel. In this way, when counting each color of the whole object, I only have to update the most present color index in the Info class to update the color of each pixel, and thus recolor the whole object.
Here is the main code with the loop:
void ProductsClipping::ProcessClippingFrame(array<unsigned char>^% CurrentProcessingFrame) {
Monitor::Enter(obj);
try {
// Update Current Buffer Index
BufferIndex++;
if (BufferIndex >= BufferSize) BufferIndex = 0;
// Get Current Frame
CurrentProductFrame = ProductResult[BufferIndex];
// Update Result Buffer Index
ResultBufferIndex++;
if (ResultBufferIndex >= BufferSize) ResultBufferIndex = 0;
// Get Result Frame
ResultFrame = ProductResult[ResultBufferIndex];
// Process each pixel
for (int i = 0; i < PixelsCount; i++) {
Product^ LastProduct = LastProductFrame[i];
if (LastProduct != nullptr && LastProduct->ProductInfo != nullptr && LastProduct->ProductInfo->BufferIndex != BufferIndex) {
LastProduct->ProductInfo->Height++;
LastProduct->ProductInfo->BufferIndex = BufferIndex;
LastProduct->ProductInfo->isResetMax = true;
LastProduct->ProductInfo->isActive = false;
LastProduct->UpdateProductIndex();
}
// If product in this pixel
if (CurrentProcessingFrame[i] > 0) {
bool hasParent = false;
// If last pixel don't belong to this product
if (CurrentProductFrame[i] == nullptr) CurrentProductFrame[i] = gcnew Product;
Product^ CurrentProduct = CurrentProductFrame[i];
// Check if has a particle in above pixel
if (LastProduct != nullptr && LastProduct->ProductID > 0) {
CurrentProduct->Update_Info(LastProduct->ProductInfo, LastProduct->ProductID, false);
hasParent = true;
}
Product^ PreviousProduct = i > 0 ? CurrentProductFrame[i - 1] : nullptr;
// Check if is a particle in the left of the pixel
if (PreviousProduct != nullptr && PreviousProduct->ProductID > 0) {
// If has particle above and in the left of pixel and the two particle is not the same
if (hasParent) {
if (PreviousProduct->ProductID != CurrentProduct->ProductID) {
PreviousProduct->Update_Info(CurrentProduct->ProductInfo, CurrentProduct->ProductID, true);
}
} else {
CurrentProduct->Update_Info(PreviousProduct->ProductInfo, PreviousProduct->ProductID, false);
hasParent = true;
}
}
if (!hasParent) {
if (i + 1 < CurrentProcessingFrame->Length && CurrentProcessingFrame[i + 1] > 0) {
CurrentProductFrame[i + 1] = CurrentProduct;
// End of product
if (LastProduct != nullptr && LastProduct->IsEndOfProduct && i == LastProduct->ProductInfo->LastMax) {
LastProduct->Close();
}
continue;
} else {
CurrentProduct->ProductID = ProductID;
ProductID++;
}
}
// If first pixel of product. Initialize information data
if (CurrentProduct->ProductInfo == nullptr) {
CurrentProduct->Initialize_Info(gcnew Info());
CurrentProduct->ProductInfo->ID = CurrentProduct->ProductID;
CurrentProduct->ProductInfo->BufferIndex = BufferIndex;
}
CurrentProduct->ProductInfo->LastMax = (CurrentProduct->ProductInfo->isResetMax) ? i : Math::Max(i, CurrentProduct->ProductInfo->LastMax);
}
// If End of product
if (LastProduct != nullptr && LastProduct->IsEndOfProduct && i == LastProduct->ProductInfo->LastMax) LastProduct->Close();
ResultFrame[i] = nullptr;
}
LastProductFrame = CurrentProductFrame;
} finally { Monitor::Exit(obj); }
}
This is the code of the Product class with the Info pointer.
ref class Product {
private:
Info^ *_ProductInfo = nullptr;
public:
unsigned int ProductID;
property Info^ ProductInfo {
Info^ get() {
if (!_ProductInfo) return nullptr;
return *_ProductInfo;
}
}
property bool IsEndOfProduct {
bool get() { return ProductInfo != nullptr && ProductInfo->isOpen && !ProductInfo->isActive; }
}
void Update_Info(Info^ NewInfo, unsigned int NewProductID, bool isBelongOtherProduct) {
if (NewInfo != nullptr) NewInfo->isActive = true;
ProductID = NewProductID;
_ProductInfo = &NewInfo;
}
void Initialize_Info(Info^ NewInfo) {
_ProductInfo = &NewInfo;
}
void Close() {
ProductInfo->isOpen = false;
}
};
The issue I experience is that for the first pixel in the object, the pointer points correctly towards the Info class, but on the next iteration, when wanting to set the next pixel to the same Info class, the first pointer becomes nullptr
EDIT: Added test code calling the loop:
public partial class Form1 : Form
{
byte[][] Frame = new byte[][]
{
new byte[]{ 0, 0, 1, 0, 0, 0, 1, 0, 0, 0 },
new byte[]{ 0, 1, 1, 0, 0, 1, 1, 0, 0, 0 },
new byte[]{ 0, 1, 1, 1, 1, 1, 1, 1, 0, 0 },
new byte[]{ 0, 0, 1, 1, 1, 1, 0, 1, 0, 0 },
new byte[]{ 0, 0, 0, 1, 0, 1, 0, 1, 0, 0 },
new byte[]{ 0, 0, 1, 0, 0, 1, 1, 1, 0, 0 },
new byte[]{ 0, 0, 1, 1, 1, 1, 1, 0, 0, 0 },
new byte[]{ 0, 0, 0, 0, 0, 0, 1, 1, 0, 0 },
new byte[]{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
new byte[]{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
new byte[]{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
new byte[]{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};
ProductsClipping ClippingFrame;
public Form1()
{
InitializeComponent();
int NbArrays = Frame.Length;
for (int i = 0; i < NbArrays; i++)
{
ClippingFrame.ProcessClippingFrame(ref Frame[i]);
}
}
}

Why is an XImage's data pointer null when capturing using XShmGetImage?

I'm writing a program to rapidly capture images from one window, modify them, and output them to another window using Xlib with C++. I have this working via XGetImage:
#include <iostream>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
const int TEST_SIZE = 512;
int main()
{
// Open default display
Display *display = XOpenDisplay(nullptr);
int screen = DefaultScreen(display);
Window rootWin = RootWindow(display, screen);
GC graphicsContext = DefaultGC(display, screen);
// Create new window and subscribe to events
long blackPixel = BlackPixel(display, screen);
long whitePixel = WhitePixel(display, screen);
Window newWin = XCreateSimpleWindow(display, rootWin, 0, 0, TEST_SIZE, TEST_SIZE, 1, blackPixel, whitePixel);
XMapWindow(display, newWin);
XSelectInput(display, newWin, ExposureMask | KeyPressMask);
// Main event loop for new window
XImage *image;
XEvent event;
bool exposed = false;
bool killWindow = false;
while (!killWindow)
{
// Handle pending events
if (XPending(display) > 0)
{
XNextEvent(display, &event);
if (event.type == Expose)
{
exposed = true;
} else if (event.type == NoExpose)
{
exposed = false;
} else if (event.type == KeyPress)
{
killWindow = true;
}
}
// Capture the original image
image = XGetImage(display, rootWin, 0, 0, TEST_SIZE, TEST_SIZE, AllPlanes, ZPixmap);
// Modify the image
if (image->data != nullptr)
{
long pixel = 0;
for (int x = 0; x < image->width; x++)
{
for (int y = 0; y < image->height; y++)
{
// Invert the color of each pixel
pixel = XGetPixel(image, x, y);
XPutPixel(image, x, y, ~pixel);
}
}
}
// Output the modified image
if (exposed && killWindow == false)
{
XPutImage(display, newWin, graphicsContext, image, 0, 0, 0, 0, TEST_SIZE, TEST_SIZE);
}
XDestroyImage(image);
}
// Goodbye
XCloseDisplay(display);
}
It generates output like this: https://streamable.com/hovg9
I'm happy to have gotten this far, but from what I've read the performance isn't going to scale very well because it has to allocate space for a new image at every frame. In fact, without the call to XDestroyImage at the end of the loop this program fills up all 16GB of memory on my machine in a matter of seconds!
It seems the recommended approach here is to to set up a shared memory space where X can write the contents of each frame and my program can subsequently read and modify them without the need for any extra allocation. Since the call to XShmGetImage blocks and waits for IPC I believe this means I won't have to worry about any concurrency issues in the shared space.
I've attempted to implement the XShmGetImage approach with the following code:
#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <X11/Xlib.h>
#include <X11/extensions/XShm.h>
#include <X11/Xutil.h>
const int TEST_SIZE = 512;
int main()
{
// Open default display
Display *display = XOpenDisplay(nullptr);
int screen = DefaultScreen(display);
Window rootWin = RootWindow(display, screen);
GC graphicsContext = DefaultGC(display, screen);
// Create new window and subscribe to events
long blackPixel = BlackPixel(display, screen);
long whitePixel = WhitePixel(display, screen);
Window newWin = XCreateSimpleWindow(display, rootWin, 0, 0, TEST_SIZE, TEST_SIZE, 1, blackPixel, whitePixel);
XMapWindow(display, newWin);
XSelectInput(display, newWin, ExposureMask | KeyPressMask);
// Allocate shared memory for image capturing
Visual *visual = DefaultVisual(display, 0);
XShmSegmentInfo shminfo;
int depth = DefaultDepth(display, screen);
XImage *image = XShmCreateImage(display, visual, depth, ZPixmap, nullptr, &shminfo, TEST_SIZE, TEST_SIZE);
shminfo.shmid = shmget(IPC_PRIVATE, image->bytes_per_line * image->height, IPC_CREAT | 0666);
shmat(shminfo.shmid, nullptr, 0);
shminfo.shmaddr = image->data;
shminfo.readOnly = False;
XShmAttach(display, &shminfo);
// Main event loop for new window
XEvent event;
bool exposed = false;
bool killWindow = false;
while (!killWindow)
{
// Handle pending events
if (XPending(display) > 0)
{
XNextEvent(display, &event);
if (event.type == Expose)
{
exposed = true;
} else if (event.type == NoExpose)
{
exposed = false;
} else if (event.type == KeyPress)
{
killWindow = true;
}
}
// Capture the original image
XShmGetImage(display, rootWin, image, 0, 0, AllPlanes);
// Modify the image
if (image->data != nullptr) // NEVER TRUE. DATA IS ALWAYS NULL!
{
long pixel = 0;
for (int x = 0; x < image->width; x++)
{
for (int y = 0; y < image->height; y++)
{
// Invert the color of each pixel
pixel = XGetPixel(image, x, y);
XPutPixel(image, x, y, ~pixel);
}
}
}
// Output the modified image
if (exposed && killWindow == false)
{
XShmPutImage(display, newWin, graphicsContext, image, 0, 0, 0, 0, 512, 512, false);
}
}
// Goodbye
XFree(image);
XCloseDisplay(display);
}
Somehow, the images are still being captured and sent to the new window, but the data pointer within the XImage is always null. I can't modify any of the contents and any usage of the XGetPixel and XPutPixel macros will fail. Notice in this video that none of the colors are being inverted like before: https://streamable.com/dckyv
This doesn't make any sense to me. Clearly the data is still being transferred between the windows, but where is it in the XImage structure? How can I access it via code?
It turns out I wasn't reading the signature for shmat correctly. It doesn't have a void return type, it returns a void pointer. This needs to be assigned directly to the XImage's data pointer in order for the data pointer to do anything.
In the call to XShmPutImage the shared memory location is referenced internally rather than the XImage's data pointer which is why this was still working even without utilizing the return value from shmat.
Here's the working code, along with a few other additions which I made for error handling and teardown:
#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <X11/Xlib.h>
#include <X11/extensions/XShm.h>
#include <X11/Xutil.h>
const int TEST_SIZE = 512;
static int handleXError(Display *display, XErrorEvent *event)
{
printf("XErrorEvent triggered!\n");
printf("error_code: %d", event->error_code);
printf("minor_code: %d", event->minor_code);
printf("request_code: %d", event->request_code);
printf("resourceid: %lu", event->resourceid);
printf("serial: %d", event->error_code);
printf("type: %d", event->type);
return 0;
}
int main()
{
// Open default display
XSetErrorHandler(handleXError);
Display *display = XOpenDisplay(nullptr);
int screen = DefaultScreen(display);
Window rootWin = RootWindow(display, screen);
GC graphicsContext = DefaultGC(display, screen);
// Create new window and subscribe to events
long blackPixel = BlackPixel(display, screen);
long whitePixel = WhitePixel(display, screen);
Window newWin = XCreateSimpleWindow(display, rootWin, 0, 0, TEST_SIZE, TEST_SIZE, 1, blackPixel, whitePixel);
XMapWindow(display, newWin);
XSelectInput(display, newWin, ExposureMask | KeyPressMask);
// Allocate shared memory for image capturing
XShmSegmentInfo shminfo;
Visual *visual = DefaultVisual(display, screen);
int depth = DefaultDepth(display, screen);
XImage *image = XShmCreateImage(display, visual, depth, ZPixmap, nullptr, &shminfo, TEST_SIZE, TEST_SIZE);
shminfo.shmid = shmget(IPC_PRIVATE, image->bytes_per_line * image->height, IPC_CREAT | 0777);
image->data = (char*)shmat(shminfo.shmid, 0, 0);
shminfo.shmaddr = image->data;
shminfo.readOnly = False;
XShmAttach(display, &shminfo);
XSync(display, false);
shmctl(shminfo.shmid, IPC_RMID, 0);
// Main event loop for new window
XEvent event;
bool exposed = false;
bool killWindow = false;
while (!killWindow)
{
// Handle pending events
if (XPending(display) > 0)
{
XNextEvent(display, &event);
if (event.type == Expose)
{
exposed = true;
} else if (event.type == NoExpose)
{
exposed = false;
} else if (event.type == KeyPress)
{
killWindow = true;
}
}
// Capture the original image
XShmGetImage(display, rootWin, image, 0, 0, AllPlanes);
// Modify the image
if(image->data != nullptr) // NEVER TRUE. DATA IS ALWAYS NULL!
{
long pixel = 0;
for (int x = 0; x < image->width; x++)
{
for (int y = 0; y < image->height; y++)
{
// Invert the color of each pixel
pixel = XGetPixel(image, x, y);
XPutPixel(image, x, y, ~pixel);
}
}
}
// Output the modified image
if (exposed && killWindow == false)
{
XShmPutImage(display, newWin, graphicsContext, image, 0, 0, 0, 0, 512, 512, false);
}
}
// Goodbye
XShmDetach(display, &shminfo);
XDestroyImage(image);
shmdt(shminfo.shmaddr);
XCloseDisplay(display);
}