const char * UsageLines [] = {
	"Usage: p4erase (P4 pbm eraser mask file for erasing)",
	"Reads P6 ppm image from standard input.  For '1' pixels",
	"on the mask file, replaces the input with the average of",
	"adjacent input pixels.  For '0' pixels on the mask file,",
	"writes input pixels to output unchanged.",
	"Writes PPM image to standard output.",
	"Input and eraser mask file must be same dimensions.",
	"May 12, 2011.  Newest is at gopher -p users/julianbr sdf.org",
	};
const int NumUsageLines = sizeof (UsageLines)/sizeof (UsageLines [0] );

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


unsigned char * * ReadEraser (
		int * EraserWidthPtr,
		int * EraserHeightPtr,
		FILE * EraserHdl)
	{
	unsigned char * * EraserLines;
	int c, i;

	if (fgetc (EraserHdl) != 'P'
			|| fgetc (EraserHdl) != '4'
			|| fscanf (EraserHdl, "%d", EraserWidthPtr) != 1
			|| fscanf (EraserHdl, "%d", EraserHeightPtr) != 1
			|| fgetc (EraserHdl) != '\n'
			|| EraserWidthPtr [0] < 1
			|| EraserHeightPtr [0] < 1) {
		fprintf (stderr, "***p4erase: Improper eraser,");
		fprintf (stderr, " must be P4 PBM.\n");
		EraserLines = NULL;
		return EraserLines;
		}
	EraserLines = malloc (EraserHeightPtr [0]*sizeof (EraserLines [0] ) );
	if (EraserLines == NULL) {
		fprintf (stderr, "***p4erase: Not enough memory.\n");
		return EraserLines;
		}
	i = 0;
	while (i < EraserHeightPtr [0] ) {
		EraserLines [i] = malloc (1 + (EraserWidthPtr [0] - 1)/8);
		if (EraserLines [i] == NULL) {
			while (i > 0) {
				i--;
				free (EraserLines [i] );
				}
			free (EraserLines);
			EraserLines = NULL;
			fprintf (stderr, "***p4erase: Not enough memory.\n");
			return EraserLines;
			}
		i++;
		}
	i = 0;
	while (i < EraserHeightPtr [0] ) {
		if (fread (EraserLines [i], 1 + (EraserWidthPtr [0] - 1)/8,
					1, EraserHdl) < 1) {
			for (i = 0; i < EraserHeightPtr [0]; i++)
				free (EraserLines [i] );
			free (EraserLines);
			EraserLines = NULL;
			fprintf (stderr, "***p4erase: Unexpected end");
			fprintf (stderr, " of eraser image data.\n");
			return EraserLines;
			}
		i++;
		}
	c = fgetc (EraserHdl);
	if (c != EOF) {
		for (i = 0; i < EraserHeightPtr [0]; i++)
			free (EraserLines [i] );
		free (EraserLines);
		EraserLines = NULL;
		fprintf (stderr, "***p4erase: Excess eraser");
		fprintf (stderr, " image data.\n");
		return EraserLines;
		}
	return EraserLines;
	}


unsigned char * * ReadInput (int * InputWidthPtr, int * InputHeightPtr)
	{
	unsigned char * * InputLines;
	int InputDepth;
	int c, i;

	if (getchar () != 'P'
			|| getchar () != '6'
			|| scanf ("%d", InputWidthPtr) != 1
			|| scanf ("%d", InputHeightPtr) != 1
			|| scanf ("%d", & InputDepth) != 1
			|| getchar () != '\n'
			|| InputWidthPtr [0] < 1
			|| InputHeightPtr [0] < 1) {
		fprintf (stderr, "***p4erase: Improper input,");
		fprintf (stderr, " must be P6 PPM.\n");
		InputLines = NULL;
		return InputLines;
		}
	InputLines = malloc (InputHeightPtr [0]*sizeof (InputLines [0] ) );
	if (InputLines == NULL) {
		fprintf (stderr, "***p4erase: Not enough memory.\n");
		return InputLines;
		}
	i = 0;
	while (i < InputHeightPtr [0] ) {
		InputLines [i] = malloc (3*InputWidthPtr [0] );
		if (InputLines [i] == NULL) {
			while (i > 0) {
				i--;
				free (InputLines [i] );
				}
			free (InputLines);
			InputLines = NULL;
			fprintf (stderr, "***p4erase: Not enough memory.\n");
			return InputLines;
			}
		i++;
		}
	i = 0;
	while (i < InputHeightPtr [0] ) {
		if (fread (InputLines [i], 3*InputWidthPtr [0],
					1, stdin) < 1) {
			for (i = 0; i < InputHeightPtr [0]; i++)
				free (InputLines [i] );
			free (InputLines);
			InputLines = NULL;
			fprintf (stderr, "***p4erase: Unexpected end");
			fprintf (stderr, " of input image data.\n");
			return InputLines;
			}
		i++;
		}
	c = getchar ();
	if (c != EOF) {
		for (i = 0; i < InputHeightPtr [0]; i++)
			free (InputLines [i] );
		free (InputLines);
		InputLines = NULL;
		fprintf (stderr, "***p4erase: Excess input");
		fprintf (stderr, " image data.\n");
		}
	return InputLines;
	}


void EraseData (
		unsigned char * * StillToErase,
		unsigned char * * InputLines,
		int width,
		int height)
	{
	unsigned char * * NextToErase;
	unsigned int r, g, b;
	int AnyMorePixels, i, j, NumAdjacent, AllAreErased;

	NextToErase = malloc (height*sizeof (NextToErase [0] ) );
	if (NextToErase == NULL) {
		fprintf (stderr, "***p4erase: Not enough memory.\n");
		return;
		}
	i = 0;
	while (i < height) {
		NextToErase [i] = malloc (1 + (width - 1)/8);
		if (NextToErase [i] == NULL) {
			while (i > 0) {
				i--;
				free (NextToErase [i] );
				}
			free (NextToErase);
			fprintf (stderr, "***p4erase: Not enough memory.\n");
			return;
			}
		i++;
		}
	for (i = 0; i < height; i++)
		memcpy (NextToErase [i], StillToErase [i],
				1 + (width - 1)/8);
	AllAreErased = 0;
	AnyMorePixels = 1;
	while (AnyMorePixels && !AllAreErased) {
		AllAreErased = 1;
		AnyMorePixels = 0;
		for (i = 0; i < height; i++) {
			for (j = 0; j < width; j++) {
				if ((StillToErase [i] [j/8]
							& (1 << (7 - j%8) ) )
						> 0) {
					r = 0;
					g = 0;
					b = 0;
					NumAdjacent = 0;
					if (i > 0 && j > 0
					&& (StillToErase [i - 1] [(j - 1)/8]
					& (1 << (7 - (j - 1)%8) ) ) == 0) {
						r += InputLines
						[i - 1] [3*(j - 1) + 0];
						g += InputLines
						[i - 1] [3*(j - 1) + 1];
						b += InputLines
						[i - 1] [3*(j - 1) + 2];
						NumAdjacent++;
						}
					if (i > 0
					&& (StillToErase [i - 1] [j/8]
					& (1 << (7 - j%8) ) ) == 0) {
						r += InputLines
						[i - 1] [3*j + 0];
						g += InputLines
						[i - 1] [3*j + 1];
						b += InputLines
						[i - 1] [3*j + 2];
						NumAdjacent++;
						}
					if (i > 0 && j < width - 1
					&& (StillToErase [i - 1] [(j + 1)/8]
					& (1 << (7 - (j + 1)%8) ) ) == 0) {
						r += InputLines
						[i - 1] [3*(j + 1) + 0];
						g += InputLines
						[i - 1] [3*(j + 1) + 1];
						b += InputLines
						[i - 1] [3*(j + 1) + 2];
						NumAdjacent++;
						}
					if (j > 0
					&& (StillToErase [i] [(j - 1)/8]
					& (1 << (7 - (j - 1)%8) ) ) == 0) {
						r += InputLines
						[i] [3*(j - 1) + 0];
						g += InputLines
						[i] [3*(j - 1) + 1];
						b += InputLines
						[i] [3*(j - 1) + 2];
						NumAdjacent++;
						}
					if (j < width - 1
					&& (StillToErase [i] [(j + 1)/8]
					& (1 << (7 - (j + 1)%8) ) ) == 0) {
						r += InputLines
						[i] [3*(j + 1) + 0];
						g += InputLines
						[i] [3*(j + 1) + 1];
						b += InputLines
						[i] [3*(j + 1) + 2];
						NumAdjacent++;
						}
					if (i < height - 1 && j > 0
					&& (StillToErase [i + 1] [(j - 1)/8]
					& (1 << (7 - (j - 1)%8) ) ) == 0) {
						r += InputLines
						[i + 1] [3*(j - 1) + 0];
						g += InputLines
						[i + 1] [3*(j - 1) + 1];
						b += InputLines
						[i + 1] [3*(j - 1) + 2];
						NumAdjacent++;
						}
					if (i < height - 1 
					&& (StillToErase [i + 1] [j/8]
					& (1 << (7 - j%8) ) ) == 0) {
						r += InputLines
						[i + 1] [3*j + 0];
						g += InputLines
						[i + 1] [3*j + 1];
						b += InputLines
						[i + 1] [3*j + 2];
						NumAdjacent++;
						}
					if (i < height - 1 && j < width - 1
					&& (StillToErase [i + 1] [(j + 1)/8]
					& (1 << (7 - (j + 1)%8) ) ) == 0) {
						r += InputLines
						[i + 1] [3*(j + 1) + 0];
						g += InputLines
						[i + 1] [3*(j + 1) + 1];
						b += InputLines
						[i + 1] [3*(j + 1) + 2];
						NumAdjacent++;
						}
					if (NumAdjacent == 0)
						AnyMorePixels = 1;
					else {
						NextToErase [i] [j/8]
							&= ~(1 << (7 - j%8) );
						InputLines [i] [3*j + 0]
							= (r + NumAdjacent/2)
							/NumAdjacent;
						InputLines [i] [3*j + 1]
							= (g + NumAdjacent/2)
							/NumAdjacent;
						InputLines [i] [3*j + 2]
							= (b + NumAdjacent/2)
							/NumAdjacent;
						}
					}
				else
					AllAreErased = 0;
				}
			}
		if (AnyMorePixels) {
			for (i = 0; i < height; i++)
				memcpy (StillToErase [i], NextToErase [i],
						1 + (width - 1)/8);
			}
		}
	if (AllAreErased) {
		fprintf (stderr, "***p4erase: Cannot erase the entire");
		fprintf (stderr, " input image.\n");
		}
	for (i = 0; i < height; i++)
		free (NextToErase [i] );
	free (NextToErase);
	}


void WriteOutput (
		unsigned char * * outputLines,
		int outputWidth,
		int outputHeight)
	{
	int i;

	printf ("P6\n");
	printf ("%d %d\n", outputWidth, outputHeight);
	printf ("%d\n", 255);
	for (i = 0; i < outputHeight; i++)
		fwrite (outputLines [i], 3*outputWidth, 1, stdout);
	}


void Erase (char * EraserFile)
	{
	FILE * EraserHdl;
	unsigned char * * EraserLines, * * InputLines;
	int EraserWidth, EraserHeight, InputWidth, InputHeight, i;

	EraserLines = NULL;
	EraserHdl = fopen (EraserFile, "rb");
	if (EraserHdl == NULL) {
		fprintf (stderr, "***p4erase:");
		fprintf (stderr, " \"%s\"", EraserFile);
		fprintf (stderr, " not found.\n");
		}
	else {
		EraserLines = ReadEraser (
				& EraserWidth,
				& EraserHeight,
				EraserHdl);
		fclose (EraserHdl);
		}
	InputLines = ReadInput (& InputWidth, & InputHeight);
	if (EraserLines != NULL && InputLines != NULL) {
		if (EraserWidth != InputWidth || EraserHeight != InputHeight) {
			fprintf (stderr, "***p4erase: Eraser dimensions");
			fprintf (stderr, " %dx%d", EraserWidth, EraserHeight);
			fprintf (stderr, " don't match input dimensions");
			fprintf (stderr, " %dx%d.\n", InputWidth, InputHeight);
			}
		else {
			EraseData (
					EraserLines,
					InputLines,
					EraserWidth,
					EraserHeight);
			WriteOutput (
					InputLines,
					EraserWidth,
					EraserHeight);
			}
		}
	if (EraserLines != NULL) {
		for (i = 0; i < EraserHeight; i++)
			free (EraserLines [i] );
		free (EraserLines);
		}
	if (InputLines != NULL) {
		for (i = 0; i < InputHeight; i++)
			free (InputLines [i] );
		free (InputLines);
		}
	}


int main (int argc, char * argv [] )
	{
	int i;

	if (argc < 2) {
		for (i = 0; i < NumUsageLines; i++)
			printf ("%s\n", UsageLines [i] );
		}
	else if (argc == 2)
		Erase (argv [1] );
	else {
		fprintf (stderr, "Usage: p4erase");
		fprintf (stderr, " (eraser file)\n");
		}
	return 0;
	}