⛏️ index : haiku.git

/*
 * Copyright 2007, Haiku. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Michael Pfeiffer
 */

#include <GraphicsDefs.h>
#include <InterfaceKit.h>
#include <String.h>

#include <stdio.h>

#include "PictureTest.h"

#define TEST_AND_RETURN(condition, message, result) \
	{ \
		if (condition) { \
			SetErrorMessage(message); \
			return result; \
		} \
	}

template <class T>
class AutoDelete
{
public:
	AutoDelete(T *object) : fObject(object) { }
	~AutoDelete() { delete fObject; fObject = NULL; }
	
	T* Release() { T* object = fObject; fObject = NULL; return object; }
	
private:
	T *fObject;
};

class OffscreenBitmap {
public:
	OffscreenBitmap(BRect frame, color_space colorSpace);
	virtual ~OffscreenBitmap();
	status_t InitCheck() const { return fStatus; }
	
	BView *View();
	BBitmap *Copy();

private:
	BRect fFrame;
	color_space fColorSpace;
	status_t fStatus;
	
	BBitmap *fBitmap;
	BView *fView;
};

OffscreenBitmap::OffscreenBitmap(BRect frame, color_space colorSpace)
	: fFrame(frame)
	, fColorSpace(colorSpace)
	, fStatus(B_ERROR)
	, fBitmap(NULL)
	, fView(NULL)
{
	BBitmap *bitmap = new BBitmap(frame, fColorSpace, true);
	AutoDelete<BBitmap> _bitmap(bitmap);
	if (bitmap == NULL || bitmap->IsValid() == false || bitmap->InitCheck() != B_OK)
		return;
	
	BView *view = new BView(frame, "offscreen", B_FOLLOW_ALL, B_WILL_DRAW);
	AutoDelete<BView> _view(view);
	if (view == NULL)
		return;
	
	bitmap->Lock();
	bitmap->AddChild(view);
	// bitmap is locked during the life time of this object
	
	fBitmap = _bitmap.Release();
	fView = _view.Release();
	fStatus = B_OK;
}

OffscreenBitmap::~OffscreenBitmap()
{
	if (fStatus != B_OK)
		return;
	
	fView->RemoveSelf();
	fBitmap->Unlock();
	delete fView;
	fView = NULL;
	
	delete fBitmap;
	fBitmap = NULL;
	
	fStatus = B_ERROR;
}

BView *
OffscreenBitmap::View()
{
	return fView;
}

BBitmap*
OffscreenBitmap::Copy()
{
	// the result bitmap that does not accept views	
	// to save resources in the application server
	BBitmap *copy = new BBitmap(fFrame, fColorSpace, false);
	AutoDelete<BBitmap> _copy(copy);
	if (copy == NULL || copy->IsValid() == false || copy->InitCheck() != B_OK)
		return NULL;

	fView->Sync();
	fBitmap->Unlock();
	
	memcpy(copy->Bits(), fBitmap->Bits(), fBitmap->BitsLength());	
	
	fBitmap->Lock();

	return _copy.Release();
}

PictureTest::PictureTest()
	: fColorSpace(B_RGBA32)
	, fDirectBitmap(NULL)
	, fBitmapFromPicture(NULL)
	, fBitmapFromRestoredPicture(NULL)
{
}


BBitmap*
PictureTest::DirectBitmap(bool detach)
{
	BBitmap* bitmap = fDirectBitmap;
	if (detach)
		fDirectBitmap = NULL;
	return bitmap;
}

BBitmap*
PictureTest::BitmapFromPicture(bool detach)
{
	BBitmap* bitmap = fBitmapFromPicture;
	if (detach)
		fBitmapFromPicture = NULL;
	return bitmap;
}


BBitmap*
PictureTest::BitmapFromRestoredPicture(bool detach)
{
	BBitmap* bitmap = fBitmapFromRestoredPicture;
	if (detach)
		fBitmapFromRestoredPicture = NULL;
	return bitmap;
}


PictureTest::~PictureTest() 
{
	CleanUp();
}

void
PictureTest::CleanUp()
{
	delete fBitmapFromPicture;
	fBitmapFromPicture = NULL;
	delete fBitmapFromRestoredPicture;
	fBitmapFromRestoredPicture = NULL;
	fErrorMessage = "";
}

void
PictureTest::SetErrorMessage(const char *message)
{
	if (fErrorMessage.Length() == 0)
		fErrorMessage = message;
}

bool
PictureTest::Test(draw_func* func, BRect frame)
{
	CleanUp();

	fDirectBitmap = CreateBitmap(func, frame);
	TEST_AND_RETURN(fDirectBitmap == NULL, "Could not create direct draw bitmap!", false);

	BPicture *picture = RecordPicture(func, frame);
	AutoDelete<BPicture> _picture(picture);
	TEST_AND_RETURN(picture == NULL, "Picture could not be recorded!", false);
		
	BPicture *archivedPicture = SaveAndRestore(picture);
	AutoDelete<BPicture> _archivedPicture(archivedPicture);
	TEST_AND_RETURN(picture == NULL, "Picture could not be flattened and unflattened!", false);
	
	fBitmapFromPicture = CreateBitmap(picture, frame);
	TEST_AND_RETURN(fBitmapFromPicture == NULL, "Could not create bitmap from original picture!", false);
		
	fBitmapFromRestoredPicture = CreateBitmap(archivedPicture, frame);
	TEST_AND_RETURN(fBitmapFromRestoredPicture == NULL, "Could not create bitmap from archived picture!", false);
	
	BString reason;
	if (!IsSame(fDirectBitmap, fBitmapFromPicture, reason)) {
		BString message("Bitmap from picture differs from direct drawing bitmap: ");
		message += reason;
		SetErrorMessage(message.String());
		return false;
	}
	if (!IsSame(fDirectBitmap, fBitmapFromRestoredPicture, reason)) {
		BString message("Bitmap from restored picture differs from direct drawing bitmap: ");
		message += reason;
		SetErrorMessage(message.String());
		return false;
	}
	return true;
}


BBitmap *
PictureTest::CreateBitmap(draw_func* func, BRect frame)
{
	OffscreenBitmap bitmap(frame, fColorSpace);
	TEST_AND_RETURN(bitmap.InitCheck() != B_OK, "Offscreen bitmap for direct drawing could not be created!" , NULL);
	func(bitmap.View(), frame);
	return bitmap.Copy();
}

BPicture *
PictureTest::RecordPicture(draw_func* func, BRect frame)
{
	OffscreenBitmap bitmap(frame, fColorSpace);
	TEST_AND_RETURN(bitmap.InitCheck() != B_OK, "Offscreen bitmap for picture recording could not be created!" , NULL);
		
	BView *view = bitmap.View();
	// record
	BPicture *picture = new BPicture();	
	view->BeginPicture(picture);
	func(view, frame);
	picture = view->EndPicture();	
	
	return picture;
}

BBitmap *
PictureTest::CreateBitmap(BPicture *picture, BRect frame)
{
	OffscreenBitmap bitmap(frame, fColorSpace);
	TEST_AND_RETURN(bitmap.InitCheck() != B_OK, "Offscreen bitmap for picture drawing could not be created!" , NULL);

	BView *view = bitmap.View();		
	view->DrawPicture(picture);
	view->Sync();
	
	return bitmap.Copy();
}


static void setMismatchReason(int32 x, int32 y, uint8 *pixel1, uint8 *pixel2, 
	int32 bpp, BString &reason)
{
	char buffer1[32];
	char buffer2[32];
	uint32 color1 = 0;
	uint32 color2 = 0;
	memcpy(&color1, pixel1, bpp);
	memcpy(&color2, pixel2, bpp);
	sprintf(buffer1, "0x%8.8x", (int)color1);
	sprintf(buffer2, "0x%8.8x", (int)color2);
	
	reason = "Pixel at ";
	reason << x << ", " << y << " differs: " << buffer1 << " != " << buffer2;
}


bool
PictureTest::IsSame(BBitmap *bitmap1, BBitmap *bitmap2, BString &reason)
{
	if (bitmap1->ColorSpace() != bitmap2->ColorSpace()) {
		reason = "ColorSpace() differs";
		return false;
	}
	
	if (bitmap1->BitsLength() != bitmap2->BitsLength()) {
		reason = "BitsLength() differs";
		return false;
	}
	
	size_t rowAlignment;
	size_t pixelChunk;
	size_t pixelsPerChunk;
	if (get_pixel_size_for(bitmap1->ColorSpace(), &pixelChunk, &rowAlignment, 
		&pixelsPerChunk) != B_OK) {
		reason = "get_pixel_size_for() not supported for this color space";
		return false;
	}
	if (pixelsPerChunk != 1) {
		reason = "Unsupported color_space; IsSame(...) supports 1 pixels per chunk only";
		return false;
	}
	int32 bpp = (int32)pixelChunk;
	uint8* row1 = (uint8*)bitmap1->Bits();
	uint8* row2 = (uint8*)bitmap2->Bits();
	int32 bpr = bitmap1->BytesPerRow();
	int32 width = bitmap1->Bounds().IntegerWidth() + 1;
	int32 height = bitmap1->Bounds().IntegerHeight() + 1;
	for (int y = 0; y < height; y ++, row1 += bpr, row2 += bpr) {
		uint8* pixel1 = row1;
		uint8* pixel2 = row2;
		for (int x = 0; x < width; x ++, pixel1 += bpp, pixel2 += bpp) {
			if (memcmp(pixel1, pixel2, bpp) != 0) {
				setMismatchReason(x, y, pixel1, pixel2, bpp, reason);
				return false;
			}
		}
	}

	reason = "";
	return true;
}


FlattenPictureTest::FlattenPictureTest()
{
}

BPicture *
FlattenPictureTest::SaveAndRestore(BPicture *picture)
{
	BMallocIO *data = new BMallocIO();
	AutoDelete<BMallocIO> _data(data);
	TEST_AND_RETURN(data == NULL, "BMallocIO could not be allocated for flattening the picture!" , NULL);
	
	picture->Flatten(data);
	
	data->Seek(0, SEEK_SET);
	BPicture *archivedPicture = new BPicture();
	TEST_AND_RETURN(archivedPicture == NULL, "BPicture could not be allocated for unflattening the picture!" , NULL);
	archivedPicture->Unflatten(data);
		
	return archivedPicture;
}

ArchivePictureTest::ArchivePictureTest()
{
}

BPicture *
ArchivePictureTest::SaveAndRestore(BPicture *picture)
{
	BMessage archive;
	TEST_AND_RETURN(picture->Archive(&archive) != B_OK, "Picture could not be archived to BMessage", NULL);

	BArchivable *archivable = BPicture::Instantiate(&archive);
	AutoDelete<BArchivable> _archivable(archivable);
	TEST_AND_RETURN(archivable == NULL, "Picture could not be instantiated from BMessage", NULL);
	
	BPicture *archivedPicture = dynamic_cast<BPicture*>(archivable);
	TEST_AND_RETURN(archivedPicture == NULL, "Picture could not be restored from BMessage", NULL);

	_archivable.Release();
	return archivedPicture;
}