// gsclib.cpp : Defines the exported functions for the DLL application.
//
#include "stdafx.h"
#include "stdlib.h"
#include "stdint.h"
#include "stdio.h"
#include <assert.h>
#include "gsclib.h"
struct FractalParams
{
COLORREF colors[16];
int expand; // palette expansion factor: <1, 64>; 1 - use basic 16 colors only, 64 - use 16 x 64 shades
int step; // RGB step for subsequent expaned color
double zr, zi;
double xmin, xmax;
double ymin, ymax;
};
COLORREF defaultColors[] =
{
RGB(0, 255, 0), // Green
RGB(255, 255, 0), // Yellow
RGB(255, 0, 0), // Red
RGB(0, 0, 255), // Blue
RGB(0, 255, 255), // Cyan
RGB(128, 128, 128), // Grey
RGB(0, 128, 128), // Aqua
RGB(255, 0, 255), // Magenta
RGB(128, 128, 0), // Brown
RGB(128, 0, 128), // Purple
RGB(255, 255, 255),
RGB(128, 0, 0),
RGB(0, 128, 0),
RGB(0, 0, 128),
RGB(192, 192, 192),
RGB(0, 0, 0) // Black
};
void DrawFractal(HDC hdc, const RECT& rect, FractalParams *p)
{
//double p = /*-1.25*/0.32, q = /*0.1*/0.043;
//double p = 0.1, q = 0.43;
//double p = p->zr;
//double q = p->zi;
double m = 100;
double zoom_a = (p->xmax - p->xmin) / (rect.right - rect.left + 1);
double zoom_b = (p->ymax - p->ymin) / (rect.bottom - rect.top + 1);
int palette = p->expand * 16;
COLORREF colorsExt[1024];
for (int c = 0; c < 16; ++c)
{
BYTE r = GetRValue(p->colors[c]);
BYTE g = GetGValue(p->colors[c]);
BYTE b = GetBValue(p->colors[c]);
colorsExt[p->expand*c + p->expand / 2] = p->colors[c];
for (int i = 1; i < p->expand / 2 + p->expand % 2; ++i)
colorsExt[p->expand*c + p->expand / 2 + i] = RGB(min(r + i * p->step, 255), min(g + i * p->step, 255), min(b + i * p->step, 255));
for (int i = 1; i < p->expand / 2 + 1; ++i)
colorsExt[p->expand*c + p->expand / 2 - i] = RGB(max((int)r - i * p->step, 0), max((int)g - i * p->step, 0), max((int)b - i * p->step, 0));
}
for (int j = rect.top; j <= rect.bottom; ++j)
for (int i = rect.left; i <= rect.right; ++i)
{
double a = p->xmin + (i - rect.left)*zoom_a;
double b = p->ymin + (j - rect.top)*zoom_b;
double aN = 0, bN = 0;
USHORT n = 0;
while (true)
{
aN = a*a - b*b + p->zr/*p*/;
bN = 2 * a*b + p->zi/*q*/;
++n;
a = aN;
b = bN;
double s = a*a + b*b;
if (m < s)
{
::SetPixelV(hdc, i, j, colorsExt[n]);
break;
}
if (m == s)
{
::SetPixelV(hdc, i, j, colorsExt[0]);
break;
}
if (n >= palette - 1)
{
::SetPixelV(hdc, i, j, colorsExt[n]);
break;
}
}
}
}
bool SaveFractalAsDIB(GSCalcArg *arg, BYTE*& dib, size_t& dibLen, const RECT& rect, FractalParams *p, uint8_t& errorCode)
{
int width = rect.right - rect.left + 1;
int height = rect.bottom - rect.top + 1;
int rowBytes = 4 * width;
dibLen = sizeof(BITMAPINFOHEADER) + height*(rowBytes + (rowBytes % 4 ? 4 - rowBytes % 4 : 0)) * sizeof(BYTE);
if (!(dib = static_cast<BYTE*>(arg->image.memory_alloc(dibLen))))
{
errorCode = ERROR1_OUT_OF_MEMORY;
return false;
}
BYTE *bits = dib + sizeof(BITMAPINFOHEADER);
BITMAPINFOHEADER *header = reinterpret_cast<BITMAPINFOHEADER*>(dib);
BITMAPINFO *bmpInfo = reinterpret_cast<BITMAPINFO*>(dib);
::memset(header, 0, sizeof(BITMAPINFOHEADER));
header->biSize = sizeof(BITMAPINFOHEADER);
header->biWidth = width;
header->biHeight = -height;
header->biCompression = BI_RGB;
header->biPlanes = 1;
header->biBitCount = 32;
header->biSizeImage = static_cast<DWORD>(dibLen) - sizeof(BITMAPINFOHEADER);
HDC screenDC = ::GetDC(NULL);
HDC memoryDC = ::CreateCompatibleDC(screenDC);
HBITMAP bmpNew = ::CreateCompatibleBitmap(screenDC, width, height);
::ReleaseDC(NULL, screenDC);
if (!memoryDC || !bmpNew)
{
arg->image.memory_free(dib);
dib = NULL;
dibLen = 0;
errorCode = ERROR1_OUT_OF_MEMORY;
if (bmpNew)
::DeleteObject(bmpNew);
if (memoryDC)
::DeleteDC(memoryDC);
return false;
}
else
{
HBITMAP bmpOld = (HBITMAP)::SelectObject(memoryDC, bmpNew);
DrawFractal(memoryDC, rect, p);
::SelectObject(memoryDC, bmpOld);
::GetDIBits(memoryDC, bmpNew, 0, height, bits, bmpInfo, DIB_RGB_COLORS);
::DeleteObject(bmpNew);
::DeleteDC(memoryDC);
return true;
}
}
extern "C" {
// fractal(zr, zi, xmin, xmax, ymin, ymax, width, height, [colors], [expand], [step])
//
// arguments:
// zr, zi - transformation
// xmin, xmax, ymin, ymax - zoom/offset
// width, height - physical bitmap size
// [colors] - 16-item RGB array/range with basic colors (optional)
// [expand] - expands the number of colors 2-64 times (optional)
// [step] - RGB step to obtain subsequent expanded colors out of the basic colors (optional)
// returns:
// a pointer to the allocated memory containing the fractal DIB bitmap;
// to obtain a higher resulution image when printing use arg->image.dims.cx/.cy to specify the display rectangle
// and width/height to specify the actual DIB bitmap dimensions
__declspec(dllexport) double __cdecl fractal(GSCalcArg *arg)
{
uint8_t errorCode = 0;
for (int i = 0; i < 8; ++i)
if (arg->types[i] != DLL_ARGT_DOUBLE)
{
errorCode = ERROR1_INVALID_VALUE;
break;
}
else if ((errorCode = arg->errors[i]) != 0)
break;
if (arg->types[11]/*...*/)
errorCode = ERROR1_SYNTAX_ERROR;
if (errorCode)
{
arg->errors[DLL_ARGC_RET] = errorCode;
return arg->types[DLL_ARGC_RET] = DLL_ARGT_EMPTY;
}
struct FractalParams p = { 0 };
RECT r = { 0, 0, 0, 0 };
p.zr = arg->numbers[0]; // 1st DLL_ARGT_DOUBLE argument
p.zi = arg->numbers[1];
p.xmin = arg->numbers[2];
p.xmax = arg->numbers[3];
p.ymin = arg->numbers[4];
p.ymax = arg->numbers[5];
r.right = static_cast<int>(arg->numbers[6]);
r.bottom = static_cast<int>(arg->numbers[7]);
if (!errorCode && !(errorCode = arg->errors[8]))
{
if (arg->types[8] == DLL_ARGT_ARRAY)
{
if (arg->array_dims[0].cy*arg->array_dims[0].cx != 16) // 0 - 1st array/range of the 11 arguments
errorCode = ERROR1_INVALID_VALUE;
ArrayItem x = { 0 };
int index = 0;
for (x.row = 0; x.row < arg->array_dims[0].cy && index < 16 && !errorCode; ++x.row)
{
for (x.col = 0; x.col < arg->array_dims[0].cx && index < 16 && !errorCode; ++x.col)
{
arg->read_array(arg->env, 0, &x);
if (x.type == DLL_ARGT_DOUBLE)
p.colors[index++] = static_cast<long>(x.num);
else if (x.type == DLL_ARGT_EMPTY)
p.colors[index++] = ::defaultColors[index];
else
errorCode = ERROR1_INVALID_VALUE;
}
}
}
else if (arg->types[8] == DLL_ARGT_EMPTY)
::memcpy(p.colors, ::defaultColors, 16 * sizeof(COLORREF));
else
errorCode = ERROR1_INVALID_VALUE;
}
if (!errorCode && !(errorCode = arg->errors[9]))
{
if (arg->types[9] == DLL_ARGT_DOUBLE)
{
p.expand = static_cast<int>(arg->numbers[8]); // 8 - 9th number of the 11 arguments
errorCode = arg->errors[8];
}
else if (arg->types[9] == DLL_ARGT_EMPTY)
p.expand = 64;
else
errorCode = ERROR1_INVALID_VALUE;
}
if (!errorCode && !(errorCode = arg->errors[10]))
{
if (arg->types[10] == DLL_ARGT_DOUBLE)
{
p.step = static_cast<int>(arg->numbers[9]); // 9 - 10th number of the 11 arguments
errorCode = arg->errors[9];
}
else if (arg->types[10] == DLL_ARGT_EMPTY)
p.step = 4;
else
errorCode = ERROR1_INVALID_VALUE;
}
assert(p.expand > 0 && p.expand <= 64 && p.xmax > p.xmin && p.ymax > p.ymin && p.step >= 1 && p.step <= 127);
if (p.expand < 1 || p.expand > 64 || p.xmax <= p.xmin || p.ymax <= p.ymin || p.step < 1 || p.step > 127 || r.right <= 0 || r.bottom <= 0)
errorCode = ERROR1_INVALID_NUMBER;
if (!errorCode)
SaveFractalAsDIB(arg, arg->image.data, arg->image.size, r, &p, errorCode);
arg->errors[DLL_ARGC_RET] = errorCode;
return arg->types[DLL_ARGC_RET] = DLL_ARGT_PNG;
}
// image(path, cx, cy)
//
// arguments:
// path - a bmp, jpg, png, gif file path
// cx/cy - display image size (optional)
// returns:
// loads an image (bmp, jpg, png, gif) from a disk and displays it, optionally resing it
// to the cx/cy dimensions (in screen pixels)
__declspec(dllexport) double __cdecl image(GSCalcArg *arg)
{
uint8_t errorCode = 0;
if (arg->types[0] != DLL_ARGT_TEXT ||
arg->types[1] != DLL_ARGT_DOUBLE && arg->types[1] != DLL_ARGT_EMPTY ||
arg->types[2] != DLL_ARGT_DOUBLE && arg->types[2] != DLL_ARGT_EMPTY)
errorCode = ERROR1_INVALID_VALUE;
if (arg->types[3]/*...*/)
errorCode = ERROR1_SYNTAX_ERROR;
if (!errorCode && arg->errors[0])
errorCode = arg->errors[0];
::strcpy_s(arg->strings[DLL_ARGC_RET], MAXINPUT, arg->strings[0]);
arg->image.dims.cx = static_cast<uint32_t>(arg->numbers[0]);
arg->image.dims.cy = static_cast<uint32_t>(arg->numbers[1]);
// arg->image.data must remain NULL
arg->errors[DLL_ARGC_RET] = errorCode;
return arg->types[DLL_ARGC_RET] = DLL_ARGT_PNG;
}
// filter(range, column, number)
//
// arguments:
// range - a range/array
// column - column index (from 0 to the number of columns in 'range' - 1)
// number - numeric value to perform equality check
// returns:
// rows of 'range' containing 'value' in the specified column
__declspec(dllexport) double __cdecl filter(GSCalcArg *arg)
{
uint8_t errorCode = 0;
if (arg->types[0] != DLL_ARGT_ARRAY || arg->types[1] != DLL_ARGT_DOUBLE || arg->types[2] != DLL_ARGT_DOUBLE)
errorCode = ERROR1_INVALID_VALUE;
if (arg->types[3]/*...*/)
errorCode = ERROR1_SYNTAX_ERROR;
if (!errorCode && arg->errors[0])
errorCode = arg->errors[0];
if (!errorCode && arg->errors[1])
errorCode = arg->errors[1];
if (!errorCode && arg->errors[2])
errorCode = arg->errors[2];
ArrayItem x = { 0 };
int outRow = 0;
for (uint32_t inRow = 0; inRow < arg->array_dims[0].cy && !errorCode; ++inRow)
{
x.row = inRow;
x.col = static_cast<int>(arg->numbers[0]);
if (arg->read_array(arg->env, 0, &x) == -1)
{
errorCode = static_cast<BYTE>(x.err_code);
break;
}
bool match = false;
if (x.type == DLL_ARGT_DOUBLE)
match = (x.num == arg->numbers[1] || x.err_code && x.err_code == arg->errors[2]);
else if (x.type == DLL_ARGT_EMPTY)
match = (arg->numbers[1] == 0);
else
errorCode = ERROR1_INVALID_VALUE;
if (match)
{
for (x.col = 0; x.col < arg->array_dims[0].cx && !errorCode; ++x.col)
{
x.row = inRow;
arg->read_array(arg->env, 0, &x);
x.row = outRow;
if (arg->write_array(arg->env, DLL_ARGC_RET, &x) == -1)
errorCode = static_cast<BYTE>(x.err_code);
}
++outRow;
}
}
arg->errors[DLL_ARGC_RET] = (!outRow ? ERROR1_NULL_VALUE : 0);
return arg->types[DLL_ARGC_RET] = DLL_ARGT_ARRAY;
return 0;
}
// messageIf(condition, text)
//
// arguments:
// condition, message text
// returns:
// if condition != 0, 'messageIf' displays 'text' as a message box after each update/recalculation
__declspec(dllexport) double __cdecl messageIf(GSCalcArg *arg)
{
uint8_t errorCode = 0;
if (arg->types[0] != DLL_ARGT_DOUBLE && arg->types[0] != DLL_ARGT_EMPTY || arg->types[1] != DLL_ARGT_TEXT)
errorCode = ERROR1_INVALID_VALUE;
if (arg->types[2]/*...*/)
errorCode = ERROR1_SYNTAX_ERROR;
if (!errorCode && arg->errors[0])
errorCode = arg->errors[0];
if (!errorCode && arg->errors[1])
errorCode = arg->errors[1];
if (!errorCode)
{
::strncpy(arg->strings[DLL_ARGC_RET], arg->strings[0], 1024);
arg->strings[DLL_ARGC_RET][1024] = 0; // actually it's guaranteed on the entry
return arg->types[DLL_ARGC_RET] = (arg->numbers[0] ? DLL_ARGT_MESSAGE : DLL_ARGT_TEXT);
}
else
{
arg->errors[DLL_ARGC_RET] = errorCode;
return arg->types[DLL_ARGC_RET] = DLL_ARGT_EMPTY;
}
return 0;
}
}