/* core.c
 * This file is part of Amsam photo cropping program
 * Copyright 2021, 2023, 2024, 2025, 2026 Nandakumar Edamana
 * Distributed under GNU General Public License, version 3
 */ 

#include <assert.h>
#include <stdarg.h>
#include <stdbool.h>

#include "core.h"

#include "pixel.h"
Pixel PIXEL_BLACK = (Pixel) { { 0, 0, 0, 1 } };
Pixel PIXEL_GRAY = (Pixel) { { 0.5, 0.5, 0.5, 1 } };
Pixel PIXEL_VPBG = (Pixel) { { 0.2, 0.2, 0.2, 1 } };
Pixel PIXEL_WHITE = (Pixel) { { 1, 1, 1, 1 } };

LinearPixel LINEAR_PIXEL_BLACK = (LinearPixel) { { 0, 0, 0, 1 } };
LinearPixel LINEAR_PIXEL_RED = (LinearPixel) { { 1, 0, 0, 1 } };
LinearPixel LINEAR_PIXEL_TRANSP = (LinearPixel) { { 0, 0, 0, 0 } };
LinearPixel LINEAR_PIXEL_WHITE = (LinearPixel) { { 1, 1, 1, 1 } };

double AA_RADIUS_CIRCLE2 = 1.41421356237309504880;

double AA_MAXDIFF_LINE = (double) 0.8;

_Bool is_double_nan(double x)
{
	_Bool stat = false;
	stat = isnan(x);
	return stat;
}

VaraRect rectangle_new_from_boundaries(int x1, int y1, int x2, int y2)
{
	return region_new(x1, y1, (x2 - x1) + 1, (y2 - y1) + 1);
}

_Bool region_is_zero(VaraRect region)
{
	return region.w <= 0 || region.h <= 0;
}

VaraRect region_new(int x, int y, int w, int h)
{
	assert(w >= 0);

	assert(h >= 0);
	VaraRect region = vara_rect_default();

	region.x = x;
	region.y = y;
	region.w = w;

	region.h = h;

	return region;
}

VaraRect union_regions(VaraRect rect1, VaraRect rect2)
{
	int x2 = imax2(rect2.x + rect2.w, rect1.x + rect1.w);
	int y2 = imax2(rect2.y + rect2.h, rect1.y + rect1.h);

	rect1.x = imin2(rect1.x, rect2.x);
	rect1.y = imin2(rect1.y, rect2.y);

	rect1.w = x2 - rect1.x;

	rect1.h = y2 - rect1.y;

	return rect1;
}

void image_construct(Image *this, int width, int height, int depth)
{
	assert(width > 0);
	assert(height > 0);

	assert(depth > 0);
	this->width = width;
	this->height = height;

	this->depth = depth;

	float *_tmp_1 = (float *) calloc((size_t) ((width * height) * PIXSTRIDE), sizeof(float));
	if (_tmp_1 == NULL) {
		perror(NULL);
		exit(EXIT_FAILURE);
	}

	this->buf = _tmp_1;
}

Image * image_Clone(Image *this)
{
	Image *newimg = (Image *) malloc(sizeof(Image));
	if (newimg == NULL) {
		perror(NULL);
		exit(EXIT_FAILURE);
	}

	image_construct(newimg, this->width, this->height, this->depth);
	image_copy_image(newimg, this);

	return newimg;
}

void image_assert_region(Image *this, VaraRect region)
{
	assert(region.x + region.w <= this->width);
	assert(region.y + region.h <= this->height);
}

void image_copy_image(Image *this, Image *src)
{
	assert(src->width == this->width);

	assert(src->height == this->height);
	memcpy((char *) (this->buf), (const char *) (src->buf), ((this->width * this->height) * PIXSTRIDE) * sizeof(float));
}

Image * image_make_clone(Image *this)
{
	Image *cln = (Image *) malloc(sizeof(Image));
	if (cln == NULL) {
		perror(NULL);
		exit(EXIT_FAILURE);
	}

	image_construct(cln, this->width, this->height, this->depth);
	image_copy_image(cln, this);

	return cln;
}

VaraRect image_copy_image_region_with_destoffset(Image *this, Image *src, VaraRect region, int destx, int desty)
{
	image_assert_region(src, region);

	int startx = imax2(0, destx + region.x);
	int starty = imax2(0, desty + region.y);

	int maxx = imin2(this->width, destx + region.x + region.w);
	int maxy = imin2(this->height, desty + region.y + region.h);

	if (maxx <= startx || maxy <= starty) {
		return region_new(0, 0, 0, 0);
	}

	assert(maxx <= this->width);

	assert(maxy <= this->height);
	for (int y = starty; y < maxy; y += 1) {
		for (int x = startx; x < maxx; x += 1) {
			nan_img_set_linear_pixel(this, x, y, nan_img_get_linear_pixel(src, x - destx, y - desty));
		}
	}

	return rectangle_new_from_boundaries(startx, starty, maxx - 1, maxy - 1);
}

VaraRect image_copy_image_region_with_scale(Image *this, Image *src, VaraRect region, int destx, int desty, double scl)
{
	assert(scl > 0);

	image_assert_region(src, region);

	int startx = imax2(0, (int) (destx + scl * region.x));
	int starty = imax2(0, (int) (desty + scl * region.y));

	int x2 = region.x + region.w;
	int y2 = region.y + region.h;

	int maxx = imin2(this->width, (int) (destx + ceil(scl * x2)));
	int maxy = imin2(this->height, (int) (desty + ceil(scl * y2)));

	if (maxx <= startx || maxy <= starty) {
		return region_new(0, 0, 0, 0);
	}

	assert(maxx <= this->width);

	assert(maxy <= this->height);
	for (int y = starty; y < maxy; y += 1) {
		for (int x = startx; x < maxx; x += 1) {
			nan_img_set_linear_pixel(this, x, y, nan_img_get_linear_pixel_for_scaling(src, (x - destx) / scl, (y - desty) / scl));
		}
	}

	return rectangle_new_from_boundaries(startx, starty, maxx - 1, maxy - 1);
}

VaraRect image_copy_image_with_destoffset(Image *this, Image *src, int destx, int desty)
{
	int startx = (destx > 0)? destx: 0;

	int starty = (desty > 0)? desty: 0;

	int maxx = imin2(this->width, destx + src->width);
	int maxy = imin2(this->height, desty + src->height);

	if (maxx <= startx || maxy <= starty) {
		return region_new(0, 0, 0, 0);
	}

	assert(maxx <= this->width);

	assert(maxy <= this->height);
	for (int y = starty; y < maxy; y += 1) {
		for (int x = startx; x < maxx; x += 1) {
			nan_img_set_linear_pixel(this, x, y, nan_img_get_linear_pixel(src, x - destx, y - desty));
		}
	}

	return rectangle_new_from_boundaries(startx, starty, maxx - 1, maxy - 1);
}

VaraRect image_copy_image_with_scale(Image *this, Image *src, int destx, int desty, double scl)
{
	assert(scl > 0);

	int startx = (destx > 0)? destx: 0;
	int starty = (desty > 0)? desty: 0;

	int maxx = imin2(this->width, (int) (destx + scl * src->width));
	int maxy = imin2(this->height, (int) (desty + scl * src->height));

	if (maxx <= startx || maxy <= starty) {
		return region_new(0, 0, 0, 0);
	}

	assert(maxx <= this->width);

	assert(maxy <= this->height);
	for (int y = starty; y < maxy; y += 1) {
		for (int x = startx; x < maxx; x += 1) {
			nan_img_set_linear_pixel(this, x, y, nan_img_get_linear_pixel_for_scaling(src, (x - destx) / scl, (y - desty) / scl));
		}
	}

	return rectangle_new_from_boundaries(startx, starty, maxx - 1, maxy - 1);
}

VaraRect image_clip_region(Image *this, VaraRect region)
{
	if (region.x < 0) {
		region.w += region.x;
		region.x = 0;
	}

	if (region.y < 0) {
		region.h += region.y;
		region.y = 0;
	}

	if (region.x + region.w > this->width) {
		region.w = this->width - region.x;
	}

	if (region.y + region.h > this->height) {
		region.h = this->height - region.y;
	}

	return region;
}

void image_copy_image_region(Image *this, Image *src, VaraRect region)
{
	assert(src->width == this->width);

	assert(src->height == this->height);
	image_assert_region(src, region);

	for (int y = region.y; y < region.y + region.h; y += 1) {
		for (int x = region.x; x < region.x + region.w; x += 1) {
			nan_img_set_linear_pixel(this, x, y, nan_img_get_linear_pixel(src, x, y));
		}
	}
}

VaraRect image_dip(Image *this, double x, double y, DrawingParams dp)
{
	switch (dp.active_tool) {
	case TOOL_BRUSH:
	{
		nan_img_thick_dot_with_scatter(this, x, y, 1, dp);
		break;
	}

	case TOOL_PENCIL:
	{
		nan_img_aaline_dot(this, (int) x, (int) y, 0, dp);
		break;
	}

	case TOOL_EYEDROPPER:
	case TOOL_FLOODFILL:
	{
		assert(false);
		break;
	}
	}

	return image_get_dirty_region_by_stroke(this, x, y, x, y, dp);
}

void image_draw_debug_region(Image *this, VaraRect region)
{
	int x2 = (region.x + region.w) - 1;
	int y2 = (region.y + region.h) - 1;

	for (int x = region.x; x <= x2; x += 1) {
		nan_img_set_linear_pixel(this, x, region.y, LINEAR_PIXEL_RED);
		nan_img_set_linear_pixel(this, x, y2, LINEAR_PIXEL_RED);
	}

	for (int y = region.y; y <= y2; y += 1) {
		nan_img_set_linear_pixel(this, region.x, y, LINEAR_PIXEL_RED);
		nan_img_set_linear_pixel(this, x2, y, LINEAR_PIXEL_RED);
	}
}

VaraRect image_get_dirty_region_by_stroke(Image *this, double lbx, double lby, double ubx, double uby, DrawingParams dp)
{
	VaraRect dirtyrect = vara_rect_default();

	switch (dp.active_tool) {
	case TOOL_BRUSH:
	{
		if (dp.brushparams.scatter_type != SCATTERTYPE_NONE) {
			double newsiz = get_effective_scatter_radius(dp);
			lbx = lbx - newsiz;
			lby = lby - newsiz;
			ubx = ubx + newsiz;
			uby = uby + newsiz;
		}

		double finalrad = effective_brush_radius(dp.brushparams.siz / 2.0, dp);
		dirtyrect = rectangle_new_from_boundaries((int) (lbx - finalrad), (int) (lby - finalrad), (int) ceil(ubx + finalrad), (int) ceil(uby + finalrad));
		break;
	}

	case TOOL_PENCIL:
	{
		dirtyrect = rectangle_new_from_boundaries((int) lbx, (int) lby, (int) ceil(ubx), (int) ceil(uby));
		break;
	}

	case TOOL_EYEDROPPER:
	case TOOL_FLOODFILL:
	{
		assert(false);
		break;
	}
	}

	return image_clip_region(this, dirtyrect);
}

VaraRect image_get_region(Image *this)
{
	VaraRect region = vara_rect_default();

	region.w = this->width;

	region.h = this->height;

	return region;
}

void image_fill(Image *this, LinearPixel pixel)
{
	for (int y = 0; y < this->height; y += 1) {
		for (int x = 0; x < this->width; x += 1) {
			nan_img_set_linear_pixel(this, x, y, pixel);
		}
	}
}

void image_fill_region(Image *this, LinearPixel pixel, VaraRect region)
{
	image_assert_region(this, region);

	int x2 = region.x + region.w;
	int y2 = region.y + region.h;

	for (int y = region.y; y < y2; y += 1) {
		for (int x = region.x; x < x2; x += 1) {
			nan_img_set_linear_pixel(this, x, y, pixel);
		}
	}
}

_Bool image_is_point_outside(Image *this, int x, int y)
{
	return ((x < 0 || y < 0) || x >= this->width) || y >= this->height;
}

VaraRect image_line(Image *this, double x1, double y1, double x2, double y2, DrawingParams dp)
{
	if (x1 == x2 && y1 == y2) {
		return image_dip(this, x1, y1, dp);
	}

	double m = (double) (y2 - y1) / (x2 - x1);

	assert(!is_double_nan(m));
	double lbx = 0.0;
	double ubx = lbx;
	double lby = lbx;
	double uby = lbx;

	if (x1 <= x2) {
		lbx = x1;
		ubx = x2;
	} else {
		lbx = x2;
		ubx = x1;
	}

	if (y1 <= y2) {
		lby = y1;
		uby = y2;
	} else {
		lby = y2;
		uby = y1;
	}

	switch (dp.active_tool) {
	case TOOL_BRUSH:
	{
		if (ubx - lbx > uby - lby) {
			nan_img_thickline_inner_by_x(this, x1, y1, x2, y2, lbx, lby, ubx, uby, m, dp);
		} else {
			nan_img_thickline_inner_by_y(this, x1, y1, x2, y2, lbx, lby, ubx, uby, m, dp);
		}
		break;
	}

	case TOOL_PENCIL:
	{
		if (ubx - lbx > uby - lby) {
			nan_img_aaline_inner_by_x_dbl(this, x1, y1, x2, y2, lbx, lby, ubx, uby, m, dp);
		} else {
			nan_img_aaline_inner_by_y_dbl(this, x1, y1, x2, y2, lbx, lby, ubx, uby, m, dp);
		}
		break;
	}

	case TOOL_EYEDROPPER:
	case TOOL_FLOODFILL:
	{
		assert(false);
		break;
	}
	}

	return image_get_dirty_region_by_stroke(this, lbx, lby, ubx, uby, dp);
}

void image_overlay_image(Image *this, Image *img)
{
	assert(img->width == this->width);

	assert(img->height == this->height);
	for (int y = 0; y < this->height; y += 1) {
		for (int x = 0; x < this->width; x += 1) {
			nan_img_over_linear_pixel(this, x, y, nan_img_get_linear_pixel(img, x, y));
		}
	}
}

void image_overlay_image_region(Image *this, Image *img, VaraRect region)
{
	image_overlay_image_region_with_alpha(this, img, 1, region);
}

void image_overlay_image_region_with_alpha(Image *this, Image *img, double alpha, VaraRect region)
{
	assert(img->width == this->width);

	assert(img->height == this->height);
	image_assert_region(img, region);

	int x2 = region.x + region.w;
	int y2 = region.y + region.h;

	for (int y = region.y; y < y2; y += 1) {
		for (int x = region.x; x < x2; x += 1) {
			LinearPixel newpix = nan_img_get_linear_pixel(img, x, y);
			for (int i = 0; i < 4; i += 1) {
				newpix.rgb[i] *= alpha;
			}

			nan_img_over_linear_pixel(this, x, y, newpix);
		}
	}
}

void image_overlay_image_with_alpha(Image *this, Image *img, double alpha)
{
	image_overlay_image_region_with_alpha(this, img, alpha, image_get_region(this));
}

void image_overlay_image_with_offset(Image *this, Image *src, int destx, int desty)
{
	int destxmin = imax2(0, destx);
	int destymin = imax2(0, desty);

	int destxmax = imin2((destx + src->width) - 1, this->width - 1);
	int destymax = imin2((desty + src->height) - 1, this->height - 1);

	for (int y = destymin; y <= destymax; y += 1) {
		for (int x = destxmin; x <= destxmax; x += 1) {
			nan_img_over_linear_pixel(this, x, y, nan_img_get_linear_pixel(src, x - destx, y - desty));
		}
	}
}

void image_alphen_image_with_offset(Image *this, Image *src, int destx, int desty)
{
	int destxmin = imax2(0, destx);
	int destymin = imax2(0, desty);

	int destxmax = imin2((destx + src->width) - 1, this->width - 1);
	int destymax = imin2((desty + src->height) - 1, this->height - 1);

	for (int y = destymin; y <= destymax; y += 1) {
		for (int x = destxmin; x <= destxmax; x += 1) {
			nan_img_alphen_pixel(this, x, y, nan_img_get_linear_pixel(src, x - destx, y - desty).rgb[3]);
		}
	}
}

void image_destruct(Image *this)
{
	if (this->buf) {
		free(this->buf);
	}
}

double effective_brush_radius(double radius, DrawingParams dp)
{
	switch (dp.brushparams.type) {
	case BRUSHTYPE_CIRCLE1:
	case BRUSHTYPE_CIRCLE1NOISE:
	{
		if (!CIRCLE1_AA_IS_INSIDE) {
			radius += dp.brushparams.aaradius_circle1;
		}
		break;
	}

	case BRUSHTYPE_CIRCLE2:
	{
		radius = AA_RADIUS_CIRCLE2 + radius;
		break;
	}

	case BRUSHTYPE_RANDSQUARE:
	{
		break;
	}
	}

	return radius;
}

double get_effective_scatter_radius(DrawingParams dp)
{
	double presfac = (dp.brushparams.scatter_take_pressure)? (dp.pressure): 1;
	return (presfac * dp.brushparams.scatter_radius) * dp.brushparams.siz;
}

Image * nan_img_new(int width, int height, int depth)
{
	Image *_tmp_1 = (Image *) malloc(sizeof(Image));
	if (_tmp_1 == NULL) {
		perror(NULL);
		exit(EXIT_FAILURE);
	}

	image_construct(_tmp_1, width, height, depth);
	return _tmp_1;
}

void nan_img_aaline_dot(Image *img, int x, int y, double diff, DrawingParams dp)
{
	if (diff <= AA_MAXDIFF_LINE) {
		double fac = 1 - (double) diff / AA_MAXDIFF_LINE;
		assert(fac <= 1 && fac >= 0);
		nan_img_over_linear_pixel(img, x, y, linear_rgbapixel_from_linear_rgbpixel(dp.fgcolor, fac));
	}
}

double distance(double xdist, double ydist)
{
	return sqrt(xdist * xdist + ydist * ydist);
}

double dist2pixmid(double x1, double y1, int x2, int y2)
{
	return distance((x2 + 0.5) - x1, (y2 + 0.5) - y1);
}

double dmax2(double a, double b)
{
	return (a > b)? a: b;
}

double dmax3(double a, double b, double c)
{
	return dmax2(dmax2(a, b), dmax2(b, c));
}

double dmin2(double a, double b)
{
	return (a < b)? a: b;
}

double dmin3(double a, double b, double c)
{
	return dmin2(dmin2(a, b), dmin2(b, c));
}

float fnonzero_or_one(float a)
{
	return (a != 0)? a: 1;
}

int imax2(int a, int b)
{
	return (a > b)? a: b;
}

int imin2(int a, int b)
{
	return (a < b)? a: b;
}

unsigned int umin2(unsigned int a, unsigned int b)
{
	return (a < b)? a: b;
}

void nan_img_aaline_inner_by_x_dbl(Image *img, double x1, double y1, double x2, double y2, double lbx, double lby, double ubx, double uby, double m, DrawingParams dp)
{
	for (int x = (int) lbx; x <= (int) ceil(ubx); x += 1) {
		double xdiff = 0.0;

		if (x < lbx) {
			xdiff = lbx - x;
		} else if (x > ubx) {
			xdiff = x - ubx;
		}

		assert(xdiff <= 1 && xdiff >= 0);
		double yreal = m * (x - x1) + y1;

		for (int y = (int) lby; y <= (int) ceil(uby); y += 1) {
			double ydiff = fabs(y - yreal);

			nan_img_aaline_dot(img, x, y, distance(xdiff, ydiff), dp);
		}
	}
}

void nan_img_aaline_inner_by_y_dbl(Image *img, double x1, double y1, double x2, double y2, double lbx, double lby, double ubx, double uby, double m, DrawingParams dp)
{
	for (int y = (int) lby; y <= (int) ceil(uby); y += 1) {
		double ydiff = 0.0;

		if (y < lby) {
			ydiff = lby - y;
		} else if (y > uby) {
			ydiff = y - uby;
		}

		assert(ydiff <= 1 && ydiff >= 0);
		double xreal = (y - y1) / m + x1;

		for (int x = (int) lbx; x <= (int) ceil(ubx); x += 1) {
			double xdiff = fabs(x - xreal);

			nan_img_aaline_dot(img, x, y, distance(xdiff, ydiff), dp);
		}
	}
}

void nan_img_thickline_inner_by_x(Image *img, double x1, double y1, double x2, double y2, double lbx, double lby, double ubx, double uby, double m, DrawingParams dp)
{
	for (int x = (int) ceil(lbx); x <= (int) floor(ubx); x += 1) {
		double yreal = m * (x - x1) + y1;

		double progress = 1;
		if (dp.brushparams.pressure_size && ubx - lbx != 0) {
			progress = ((double) x - x1) / (x2 - x1);
		}

		nan_img_thick_dot_with_scatter(img, x, yreal, progress, dp);
	}
}

void nan_img_thickline_inner_by_y(Image *img, double x1, double y1, double x2, double y2, double lbx, double lby, double ubx, double uby, double m, DrawingParams dp)
{
	for (int y = (int) ceil(lby); y <= (int) floor(uby); y += 1) {
		double xreal = (y - y1) / m + x1;

		double progress = 1;
		if (dp.brushparams.pressure_size && uby - lby != 0) {
			progress = ((double) y - y1) / (y2 - y1);
		}

		nan_img_thick_dot_with_scatter(img, xreal, y, progress, dp);
	}
}

void nan_img_thick_dot(Image *img, double x, double y, double progress, DrawingParams dp)
{
	double brush_size = dp.brushparams.siz;
	double opacity = dp.brushparams.opacity;

	if (dp.brushparams.pressure_size) {
		assert(progress >= 0 && progress <= 1);

		double curpressure = dp.oldpressure + progress * (dp.pressure - dp.oldpressure);

		assert(curpressure >= 0 && curpressure <= 1);

		brush_size = brush_size * curpressure;
	}

	switch (dp.brushparams.type) {
	case BRUSHTYPE_CIRCLE1:
	{
		nan_img_fill_circle1(img, x, y, brush_size, opacity, false, dp);
		break;
	}

	case BRUSHTYPE_CIRCLE1NOISE:
	{
		nan_img_fill_circle1(img, x, y, brush_size, opacity, true, dp);
		break;
	}

	case BRUSHTYPE_CIRCLE2:
	{
		nan_img_fill_circle2(img, x, y, brush_size, opacity, dp);
		break;
	}

	case BRUSHTYPE_RANDSQUARE:
	{
		nan_img_fill_random_square(img, x, y, brush_size, opacity, dp);
		break;
	}
	}
}

void nan_img_thick_dot_with_scatter(Image *img, double x, double y, double progress, DrawingParams dp)
{
	double dlx = x;
	double dly = y;
	double dux = x;
	double duy = y;

	if (dp.brushparams.scatter_type != SCATTERTYPE_NONE) {
		int scatter_count = (int) ceil((1 * dp.brushparams.scatter_density) * dp.brushparams.scatter_radius);

		for (int i = 0; i < scatter_count; i += 1) {
			double rx = x;
			double ry = y;

			switch (dp.brushparams.scatter_type) {
			case SCATTERTYPE_CIRCLE:
			{
				double angle = (drand48() * 2) * M_PI;

				double dist = drand48() * get_effective_scatter_radius(dp);
				rx += dist * cos(angle);
				ry += dist * sin(angle);
				break;
			}

			case SCATTERTYPE_SQUARE:
			{
				double basedist = get_effective_scatter_radius(dp);
				double dist1 = drand48() * basedist;
				double dist2 = drand48() * basedist;
				int _tmp_1;
				if (drand48() < 0.5) {
					_tmp_1 = 1;
				} else {
					_tmp_1 = -1;
				}

				rx += dist1 * _tmp_1;
				int _tmp_2;
				if (drand48() < 0.5) {
					_tmp_2 = 1;
				} else {
					_tmp_2 = -1;
				}

				ry += dist2 * _tmp_2;
				break;
			}

			case SCATTERTYPE_NONE:
			{
				assert(false);
				break;
			}
			}

			nan_img_thick_dot(img, rx, ry, progress, dp);

			if (rx < dlx) {
				dlx = rx;
			}

			if (ry < dly) {
				dly = ry;
			}

			if (rx > dux) {
				dux = rx;
			}

			if (ry > duy) {
				duy = ry;
			}
		}
	} else {
		nan_img_thick_dot(img, x, y, progress, dp);
	}
}

void nan_img_fill_circle1(Image *img, double x, double y, double brush_size, double opacity, _Bool noisy, DrawingParams dp)
{
	double radius = brush_size / 2.0;

	if (radius <= 0) {
		return;
	}

	double maxrad = effective_brush_radius(radius, dp);

	double aaradius = dmax2(dp.brushparams.aaradius_circle1, maxrad * dp.brushparams.softness);

	for (int j = (int) (y - maxrad); j <= (int) ceil(y + maxrad); j += 1) {
		for (int i = (int) (x - maxrad); i <= (int) ceil(x + maxrad); i += 1) {
			double currad = sqrt(pow(x - i, 2) + pow(y - j, 2));
			if (currad <= maxrad) {
				double alpha = 1.0;

				double raddiff = fabs(currad - maxrad);
				if (raddiff <= aaradius) {
					alpha = raddiff / aaradius;
				}

				alpha *= opacity;

				if (noisy) {
					alpha *= drand48() / NOISEDIVISOR;
				}

				assert(alpha <= 1 && alpha >= 0);
				if (dp.erase) {
					nan_img_alphen_pixel(img, i, j, alpha);
				} else {
					nan_img_over_linear_pixel(img, i, j, linear_rgbapixel_from_linear_rgbpixel(dp.fgcolor, alpha));
				}
			}
		}
	}
}

void nan_img_fill_circle2(Image *img, double x, double y, double brush_size, double opacity, DrawingParams dp)
{
	if (brush_size <= 0) {
		return;
	}

	double realrad = brush_size / 2.0;
	double maxrad = effective_brush_radius(realrad, dp);
	double maxrad_pixmid = dist2pixmid(x, y, (int) (x + maxrad), (int) y);
	double softradstart = maxrad_pixmid * (1 - dp.brushparams.softness);

	for (int j = (int) (y - maxrad); j <= (int) ceil(y + maxrad); j += 1) {
		int ybound = (j < y)? j: (j + 1);

		for (int i = (int) (x - maxrad); i <= (int) ceil(x + maxrad); i += 1) {
			double alpha = 0.0;

			int xbound = (i < x)? i: (i + 1);
			double maxradbycurpix = distance(xbound - x, ybound - y);

			if (maxradbycurpix <= realrad) {
				alpha = 1;
			} else {
				double freespace = maxradbycurpix - realrad;
				if (freespace <= AA_RADIUS_CIRCLE2) {
					alpha = 1.0 - freespace / AA_RADIUS_CIRCLE2;
				}
			}

			if (dp.brushparams.softness > 0) {
				double currad_pixmid = dist2pixmid(x, y, i, j);

				if (currad_pixmid <= maxrad_pixmid && currad_pixmid >= softradstart) {
					double fac = 1.0 - (currad_pixmid - softradstart) / (maxrad_pixmid - softradstart);
					alpha *= fac;
				}
			}

			alpha *= opacity;

			assert(alpha <= 1 && alpha >= 0);
			if (alpha > 0) {
				if (dp.erase) {
					nan_img_alphen_pixel(img, i, j, alpha);
				} else {
					nan_img_over_linear_pixel(img, i, j, linear_rgbapixel_from_linear_rgbpixel(dp.fgcolor, alpha));
				}
			}
		}
	}
}

void nan_img_fill_random_square(Image *img, double x, double y, double brush_size, double opacity, DrawingParams dp)
{
	int radius = (int) (brush_size / 2.0);

	if (radius <= 0) {
		return;
	}

	int w = 2 * radius;

	Image *brushimg = (Image *) malloc(sizeof(Image));
	if (brushimg == NULL) {
		perror(NULL);
		exit(EXIT_FAILURE);
	}

	image_construct(brushimg, w, w, 255);
	image_fill(brushimg, LINEAR_PIXEL_TRANSP);

	for (int j = 0; j < w; j += 1) {
		for (int i = 0; i < w; i += 1) {
			nan_img_over_linear_pixel(brushimg, i, j, linear_rgbapixel_from_linear_rgbpixel(dp.fgcolor, opacity * (drand48() / NOISEDIVISOR)));
		}
	}

	if (dp.erase) {
		image_alphen_image_with_offset(img, brushimg, (int) (x - radius), (int) (y - radius));
	} else {
		image_overlay_image_with_offset(img, brushimg, (int) (x - radius), (int) (y - radius));
	}

	if (brushimg) {
		image_destruct(brushimg);
		free(brushimg);
	}
}

_Bool linear_pixels_match(LinearPixel px1, LinearPixel px2)
{
	for (int i = 0; i < 4; i += 1) {
		if (px1.rgb[i] != px2.rgb[i]) {
			return false;
		}
	}

	return true;
}

_Bool pixelsmatch(Pixel px1, Pixel px2)
{
	for (int i = 0; i < 4; i += 1) {
		if (px1.rgb[i] != px2.rgb[i]) {
			return false;
		}
	}

	return true;
}

void nan_copy_pixel_to_argb32(LinearPixel pixel, unsigned char *dest)
{
	for (int i = 0; i < 3; i += 1) {
		dest[2 - i] = (unsigned char) (255 * (pixel.rgb[3] * linear2srgb(pixel.rgb[i] / fnonzero_or_one(pixel.rgb[3]))));
	}

	dest[3] = (unsigned char) (255 * pixel.rgb[3]);
}

void nan_img_copy_pixel_to_argb32(Image *img, int x, int y, unsigned char *dest)
{
	LinearPixel pix = nan_img_get_linear_pixel(img, x, y);
	nan_copy_pixel_to_argb32(pix, dest);
}

void nan_copy_pixel_to_rgb24(LinearPixel pixel, unsigned char *dest)
{
	for (int i = 0; i < 3; i += 1) {
		dest[2 - i] = (unsigned char) (255 * linear2srgb(pixel.rgb[i]));
	}
}

void nan_img_copy_pixel_to_rgb24(Image *img, int x, int y, unsigned char *dest)
{
	LinearPixel pix = nan_img_get_linear_pixel(img, x, y);
	nan_copy_pixel_to_rgb24(pix, dest);
}

void print_region(VaraRect region, const char *label)
{
	fprintf(stderr, "%s: (%d, %d) w: %d, h: %d)\n", label, region.x, region.y, region.w, region.h);
}

void nan_img_draw_circle(Image *img, int x, int y, int radius, int aaradius)
{
	if (radius <= 0) {
		return;
	}

	for (int j = (int) (y - radius); j < (int) (y + radius); j += 1) {
		for (int i = (int) (x - radius); i < (int) (x + radius); i += 1) {
			double currad = distance(i - x, j - y);
			double raddiff = fabs(currad - radius);

			if (raddiff > aaradius) {
				continue;
			}

			double alpha = 1 - raddiff / aaradius;

			nan_img_set_linear_pixel(img, i, j, linear_rgbapixel_from_linear_rgbpixel(LINEAR_PIXEL_BLACK, alpha));
		}
	}
}

int roundedpercsiz(double perc, int canvsiz)
{
	return (int) ceil(perc * canvsiz);
}

VaraRect vara_rect_default()
{
	VaraRect s;
	s.x = 0;
	s.y = 0;
	s.w = 0;
	s.h = 0;
	return s;
}

DrawingParams drawing_params_default()
{
	DrawingParams s;
	s.active_tool = TOOL_BRUSH;
	s.brushparams = brush_params_default();
	s.fgcolor = linear_pixel_default();
	s.erase = false;
	s.pressure = 0.0;
	s.oldpressure = 0.0;
	return s;
}
