Image processing: pointer becomes nullptr without resetting it - c++

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]);
}
}
}

Related

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

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);
}

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+ -_- :

Is there a way to overload a constructor with a 2D array (int)?

When I utilize the default constructor, I expect it to call the constructor that accepts an argument; however, this does not occur correctly. When debugging, as far as I can tell it is assigning the values and then the instance simply isn't maintained. I'm not sure if I need to create a helper method instead to pass the object, array, assign out values, and then pass back the object?
My goal is to have a default constructor that passes a hard-coded set of values and then a constructor that accepts the same type of array passed as values.
I've tried passing the array as an argument for the constructors, and while it seems to work for the derived class, it does not work for the base class. I ended up moving the functionality of the overloaded constructor to the default constructor and that works correctly.
This is the base class:
// Puzzle.h
class Puzzle
{
public:
Puzzle();
Puzzle(int grid[gridLength][gridLength]);
~Puzzle();
void Print_Puzzle(); // Displays puzzle in console
protected:
int grid[gridLength][gridLength]; // Our board
private:
};
This is the definition:
Puzzle::Puzzle()
{
int grid[gridLength][gridLength] = // Taken from https://www.puzzles.ca/sudoku_puzzles/sudoku_easy_505.html
{
{ 0, 7, 0, 0, 0, 2, 0, 0, 0 },
{ 0, 9, 0, 3, 7, 0, 0, 0, 0 },
{ 0, 0, 5, 0, 8, 0, 0, 0, 1 },
{ 0, 0, 4, 7, 0, 0, 0, 0, 9 },
{ 0, 0, 0, 0, 9, 6, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 8, 6, 5, 4 },
{ 0, 2, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 1, 0, 4, 3 },
{ 4, 0, 7, 9, 5, 0, 2, 6, 0 }
};
for (int x = 0; x < gridLength; x++)
{
for (int y = 0; y < gridLength; y++)
Puzzle::grid[x][y] = grid[x][y];
}
// Puzzle::Puzzle(grid) // Doesn't work. Not sure how to properly pass the array values.
}
Puzzle::Puzzle(int grid[gridLength][gridLength])
{
for (int row = 0; row < gridLength; row++)
{
for (int col = 0; col < gridLength; col++)
this->grid[row][col] = grid[row][col];
}
}
I expect the default constructor to pass the grid variable and the receiving constructor to assign those values to the instance's member property.
A more convenient way and no less efficient is to use std::array which can be copied, so that you do not have to copy it element-wise:
#include <array>
constexpr int gridLength = 9;
using Grid = std::array<std::array<int, gridLength>, gridLength>;
Grid const grid = {{ // Taken from https://www.puzzles.ca/sudoku_puzzles/sudoku_easy_505.html
{ 0, 7, 0, 0, 0, 2, 0, 0, 0 },
{ 0, 9, 0, 3, 7, 0, 0, 0, 0 },
{ 0, 0, 5, 0, 8, 0, 0, 0, 1 },
{ 0, 0, 4, 7, 0, 0, 0, 0, 9 },
{ 0, 0, 0, 0, 9, 6, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 8, 6, 5, 4 },
{ 0, 2, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 1, 0, 4, 3 },
{ 4, 0, 7, 9, 5, 0, 2, 6, 0 }
}};
class Puzzle {
Grid grid_;
public:
Puzzle(Grid const& grid)
: grid_(grid) // Copy grid into grid_.
{}
};
int main() {
Puzzle p(grid);
}
Alternatively:
class Puzzle {
Grid grid_;
static Grid make_grid() {
Grid grid;
// Your code to fill the grid.
return grid;
}
public:
Puzzle()
: Puzzle(make_grid())
{}
Puzzle(Grid const& grid)
: grid_(grid) // Copy the grid.
{}
};
In my opinion, you are committing a design error.
Never use C arrays in C++, use std::vector or std::array instead.
Try this.
class Sudoku
{
public:
Sudoku(const std::vector<std::vector<int>> initData = { {/* Write your default values here */} })
{
data = initData;
}
private:
std::vector<std::vector<int>> data;
};
if you want to use C-like arrays you will need to understand the only way to pass arrays is via pointers, and is a messy solution.
template <uint32_t width, uint32_t height>
class Sudoku
{
public:
Sudoku(int** initData, int maxX, int maxY)
{
for (int i = 0; i < maxX; i++) {
for (int j = 0; j < maxY; j++) {
data[i][j] = initData[i][j];
}
}
}
private:
std::array<width, std::array<height, int>> data;
};

How to use an existing DL4J trained model to classify new input

I have a DL4J LSTM model that generates a binary classification of sequential input. i have trained and tested the model and am happy with the precision/recall. Now I want to use this model to predict the binary classification of new inputs. How do I do this? i.e. how do I give the trained neural network a single Input (file containing the sequence of feature rows) and get the binary classification of this input file.
Here is my original training data set iterator:
SequenceRecordReader trainFeatures = new CSVSequenceRecordReader(0, ","); //skip no header lines
try {
trainFeatures.initialize( new NumberedFileInputSplit(featureBaseDir + "/s_%d.csv", 0,this._modelDefinition.getNB_TRAIN_EXAMPLES()-1));
} catch (IOException e) {
trainFeatures.close();
throw new IOException(String.format("IO error %s. during trainFeatures", e.getMessage()));
} catch (InterruptedException e) {
trainFeatures.close();
throw new IOException(String.format("Interrupted exception error %s. during trainFeatures", e.getMessage()));
}
SequenceRecordReader trainLabels = new CSVSequenceRecordReader();
try {
trainLabels.initialize(new NumberedFileInputSplit(labelBaseDir + "/s_%d.csv", 0,this._modelDefinition.getNB_TRAIN_EXAMPLES()-1));
} catch (InterruptedException e) {
trainLabels.close();
trainFeatures.close();
throw new IOException(String.format("Interrupted exception error %s. during trainLabels initialise", e.getMessage()));
}
DataSetIterator trainData = new SequenceRecordReaderDataSetIterator(trainFeatures, trainLabels,
this._modelDefinition.getBATCH_SIZE(),this._modelDefinition.getNUM_LABEL_CLASSES(), false, SequenceRecordReaderDataSetIterator.AlignmentMode.ALIGN_END);
Here is my model:
MultiLayerConfiguration conf = new NeuralNetConfiguration.Builder()
.seed(this._modelDefinition.getRANDOM_SEED()) //Random number generator seed for improved repeatability. Optional.
.weightInit(WeightInit.XAVIER)
.updater(new Nesterovs(this._modelDefinition.getLEARNING_RATE()))
.gradientNormalization(GradientNormalization.ClipElementWiseAbsoluteValue) //Not always required, but helps with this data set
.gradientNormalizationThreshold(0.5)
.list()
.layer(0, new LSTM.Builder().activation(Activation.TANH).nIn(this._modelDefinition.getNB_INPUTS()).nOut(this._modelDefinition.getLSTM_LAYER_SIZE()).build())
.layer(1, new LSTM.Builder().activation(Activation.TANH).nIn(this._modelDefinition.getLSTM_LAYER_SIZE()).nOut(this._modelDefinition.getLSTM_LAYER_SIZE()).build())
.layer(2,new DenseLayer.Builder().nIn(this._modelDefinition.getLSTM_LAYER_SIZE()).nOut(this._modelDefinition.getLSTM_LAYER_SIZE())
.weightInit(WeightInit.XAVIER)
.build())
.layer(3, new RnnOutputLayer.Builder(LossFunctions.LossFunction.MCXENT)
.activation(Activation.SOFTMAX).nIn(this._modelDefinition.getLSTM_LAYER_SIZE()).nOut(this._modelDefinition.getNUM_LABEL_CLASSES()).build())
.pretrain(false).backprop(true).build();
I train the model over N epochs to get my optimal scores. I save the model, now I want to open the model and get classifications for new sequential feature files.
If there is an example of this - please let me know where.
thanks
anton
The answer is to feed the model the exact same input as we trained with, except set the labels to -1. The output will be an INDarray containing the probability of 0 in one array and the probability of 1 in the other array, showing up in the last sequence line.
Here is the code:
public void getOutputsForTheseInputsUsingThisNet(String netFilePath,String inputFileDir) throws Exception {
//open the network file
File locationToSave = new File(netFilePath);
MultiLayerNetwork nNet = null;
logger.info("Trying to open the model");
try {
nNet = ModelSerializer.restoreMultiLayerNetwork(locationToSave);
logger.info("Success: Model opened");
} catch (IOException e) {
throw new Exception(String.format("Unable to open model from %s because of error %s", locationToSave.getAbsolutePath(),e.getMessage()));
}
logger.info("Loading test data");
SequenceRecordReader testFeatures = new CSVSequenceRecordReader(0, ","); //skip no lines at the top - i.e. no header
try {
testFeatures.initialize(new NumberedFileInputSplit(inputFileDir + "/features/s_4180%d.csv", 0, 4));
} catch (InterruptedException e) {
testFeatures.close();
throw new Exception(String.format("IO error %s. during testFeatures", e.getMessage()));
}
logger.info("Loading label data");
SequenceRecordReader testLabels = new CSVSequenceRecordReader();
try {
testLabels.initialize(new NumberedFileInputSplit(inputFileDir + "/labels/s_4180%d.csv", 0,4));
} catch (InterruptedException e) {
testLabels.close();
testFeatures.close();
throw new IOException(String.format("Interrupted exception error %s. during testLabels initialise", e.getMessage()));
}
//DataSetIterator inputData = new Seque
logger.info("creating iterator");
DataSetIterator testData = new SequenceRecordReaderDataSetIterator(testFeatures, testLabels,
this._modelDefinition.getBATCH_SIZE(),this._modelDefinition.getNUM_LABEL_CLASSES(), false, SequenceRecordReaderDataSetIterator.AlignmentMode.ALIGN_END);
//now use it to classify some data
logger.info("classifying examples");
INDArray output = nNet.output(testData);
logger.info("outputing the classifications");
if(output==null||output.isEmpty())
throw new Exception("There is no output");
System.out.println(output);
//sample output
// [[[ 0, 0, 0, 0, 0.9882, 0, 0, 0, 0],
// [ 0, 0, 0, 0, 0.0118, 0, 0, 0, 0]],
//
// [[ 0, 0.1443, 0, 0, 0, 0, 0, 0, 0],
// [ 0, 0.8557, 0, 0, 0, 0, 0, 0, 0]],
//
// [[ 0, 0, 0, 0, 0, 0, 0, 0, 0.9975],
// [ 0, 0, 0, 0, 0, 0, 0, 0, 0.0025]],
//
// [[ 0, 0, 0, 0, 0, 0, 0.8482, 0, 0],
// [ 0, 0, 0, 0, 0, 0, 0.1518, 0, 0]],
//
// [[ 0, 0, 0, 0.8760, 0, 0, 0, 0, 0],
// [ 0, 0, 0, 0.1240, 0, 0, 0, 0, 0]]]
}

SDL / C++: How to make this function short(er)?

I have this:
void showNumbers(){
nrBtn1 = TTF_RenderText_Blended( fontnrs, "1", sdlcolors[0] );
nrBtn2 = TTF_RenderText_Blended( fontnrs, "2", sdlcolors[1] );
nrBtn3 = TTF_RenderText_Blended( fontnrs, "3", sdlcolors[2] );
nrBtn4 = TTF_RenderText_Blended( fontnrs, "4", sdlcolors[3] );
nrBtn5 = TTF_RenderText_Blended( fontnrs, "5", sdlcolors[4] );
nrBtn6 = TTF_RenderText_Blended( fontnrs, "6", sdlcolors[5] );
nrBtn7 = TTF_RenderText_Blended( fontnrs, "7", sdlcolors[6] );
nrBtn8 = TTF_RenderText_Blended( fontnrs, "8", sdlcolors[7] );
nrBtn9 = TTF_RenderText_Blended( fontnrs, "9", sdlcolors[8] );
SDL_Rect rcnrBtn1 = { 40, 32, 0, 0 };
SDL_Rect rcnrBtn2 = { 70, 32, 0, 0 };
SDL_Rect rcnrBtn3 = { 100, 32, 0, 0 };
SDL_Rect rcnrBtn4 = { 130, 32, 0, 0 };
SDL_Rect rcnrBtn5 = { 160, 32, 0, 0 };
SDL_Rect rcnrBtn6 = { 190, 32, 0, 0 };
SDL_Rect rcnrBtn7 = { 220, 32, 0, 0 };
SDL_Rect rcnrBtn8 = { 250, 32, 0, 0 };
SDL_Rect rcnrBtn9 = { 280, 32, 0, 0 };
SDL_BlitSurface(nrBtn1, NULL, screen, &rcnrBtn1); SDL_FreeSurface(nrBtn1);
SDL_BlitSurface(nrBtn2, NULL, screen, &rcnrBtn2); SDL_FreeSurface(nrBtn2);
SDL_BlitSurface(nrBtn3, NULL, screen, &rcnrBtn3); SDL_FreeSurface(nrBtn3);
SDL_BlitSurface(nrBtn4, NULL, screen, &rcnrBtn4); SDL_FreeSurface(nrBtn4);
SDL_BlitSurface(nrBtn5, NULL, screen, &rcnrBtn5); SDL_FreeSurface(nrBtn5);
SDL_BlitSurface(nrBtn6, NULL, screen, &rcnrBtn6); SDL_FreeSurface(nrBtn6);
SDL_BlitSurface(nrBtn7, NULL, screen, &rcnrBtn7); SDL_FreeSurface(nrBtn7);
SDL_BlitSurface(nrBtn8, NULL, screen, &rcnrBtn8); SDL_FreeSurface(nrBtn8);
SDL_BlitSurface(nrBtn9, NULL, screen, &rcnrBtn9); SDL_FreeSurface(nrBtn9);
}
But for 60 buttons. Is there a way how to do something like:
void showNumbers()
{
SDL_Rect rcnrBtn1 = { 40, 32, 0, 0 };
SDL_Rect rcnrBtn2 = { 70, 32, 0, 0 };
SDL_Rect rcnrBtn3 = { 100, 32, 0, 0 };
SDL_Rect rcnrBtn4 = { 130, 32, 0, 0 };
SDL_Rect rcnrBtn5 = { 160, 32, 0, 0 };
SDL_Rect rcnrBtn6 = { 190, 32, 0, 0 };
SDL_Rect rcnrBtn7 = { 220, 32, 0, 0 };
SDL_Rect rcnrBtn8 = { 250, 32, 0, 0 };
SDL_Rect rcnrBtn9 = { 280, 32, 0, 0 };
for(int x=1; x<=60;x++){
nrBtn+x = TTF_RenderText_Blended( fontnrs, x, sdlcolors[x-1] );
SDL_BlitSurface(nrBtn+x, NULL, screen, &rcnrBtn+x); SDL_FreeSurface(nrBtn+x);
}
}
You need to use an array.
E.g.
SDL_Rect rcnrBtn[60];
for(int x = 0; x < 60; x++) {
rcnrBtn[x].x = 30 * x + 10;
rcnrBtn[x].y = 32;
rcnrBtn[x].w = 100;
rcnrBtn[x].h = 24;
}
Arrays always start at 0, and this particular one ends at 59 giving a total of 60 elements.
You could do something like this :
void showNumbers() {
SDL_Surface* nrBtn = NULL;
int nbr = 1;
int x = 30; // your starting x
int i = 0;
for (; i < 60; ++i) {
nrBtn = TTF_RenderText_Blended(fontnrs, nbr_to_str(nbr), sdlcolors[i]); // You'll have to code nbr_to_str
SDL_Rect rect = {x, 32, 0, 0}; // Are you sure that width and height are 0 ?
SDL_BlitSurface(nrBtn, NULL, screen, &rect);
SDL_FreeSurface(nrBtn);
x += 30;
}
return;
}
It appears your entire function could be replaced with the following array-based variation. Assuming that your nrBtnXX variables are defined outside the function and you want to minimise the scope of changes, you should look at something like:
#define BUTTON_COUNT 60
SDL_Surface *nrBtn[BUTTON_COUNT];
void showNumbers () {
char textNum[3];
for (int i = 0; i < BUTTON_COUNT; i++) {
sprintf (textNum, "%d", i);
nrBtn[i] = TTF_RenderText_Blended( fontnrs, textNum, sdlcolors[i] );
}
SDL_Rect rcnrBtn[BUTTON_COUNT];
for (int i = 0; i < BUTTON_COUNT; i++) {
rcnrBtn[i].x = 40 + i * 30; // use formulae for all these.
rcnrBtn[i].y = 32;
rcnrBtn[i].w = 0;
rcnrBtn[i].h = 0;
}
for (int i = 0; i < BUTTON_COUNT; i++) {
SDL_BlitSurface(nrBtn[i], NULL, screen, &rcnrBtn[i]);
SDL_FreeSurface(nrBtn[i]);
}
}
The idea is to store everything in arrays so that you don't have to deal with individual variables. If the nrBtn variables are required to be a non-array, then I would set up a single array of pointers to them so this approach would still work, something like:
SDL_Surface *nrBtnPtr[] = { &nrBtn1, &nrBtn2 ..., &nrBtn60 };
You should also set the formulae intelligently for the x and y coordinates since you probably don't want a 60x1 matrix for them. Look into making it 12x5 or something equally as compact.