Firemonkey Image Library 2

Først: Det hele starter i Hovedformen, «TForm1», alle menyfunksjoner gjør det, og så jumper programmet i de fleste tilfelle til en funksjon i «DocPage» klassen. Det eneste som gjøres her er å opprette en adressepeker til neste funksjon:

// --------------------- Display Image Properties ---------------

	void __fastcall TForm1::menu_PROPERTIES_Click(TObject *Sender)         // 19
{
	 TTabItem   *myTabItemPtr;

	 // Get a pointer to active tab
	 myTabItemPtr = TabControl1->ActiveTab;

	 // Type cast to class DocPage and
	 // let DocPage do the job
	 ((DocPage *) myTabItemPtr)->ImageProperties();
}

De meste av info finnes i bitmap’en, «TBitmap», og hvordan du henter opp denne er egentlig forklart i dokumentasjonen fra Embarcadero.

// --------------------------------------------------------------------------
//  				Image Properties
// --------------------------------------------------------------------------
void __fastcall DocPage::ImageProperties()
{
	try
	{
		TForm6 *dlgProperties =  new TForm6(Application);

		dlgProperties->Image1->Bitmap = Image1->Bitmap;
		dlgProperties->Caption = _T("Image Properties");

		// Pixelformat
		TPixelFormat myPixelformat = Image1->Bitmap->PixelFormat;
		UnicodeString strPixelformat(PixelFormatToString(myPixelformat));
		dlgProperties->Edit1->Text = strPixelformat;

		// Bitmap Scale
		float BitmapScale = Image1->Bitmap->BitmapScale;
		UnicodeString strBitmapScale(BitmapScale);
		dlgProperties->Edit2->Text = strBitmapScale;

		// Bytes per line
		int BytesPerLine = Image1->Bitmap->BytesPerLine;
		UnicodeString strBytesPerLine(BytesPerLine);
		dlgProperties->Edit3->Text = strBytesPerLine;

		// Bytes per pixel
		int BytesPerPixel = Image1->Bitmap->BytesPerPixel;
		UnicodeString strBytesPerPixel(BytesPerPixel);
		dlgProperties->Edit4->Text = strBytesPerPixel;

		// Bitmap Height
		int Height = Image1->Bitmap->Height;
		UnicodeString strHeight(Height);
		dlgProperties->Edit5->Text = strHeight;

		// Bitmap Width
		int Width = Image1->Bitmap->Width;
		UnicodeString strWidth(Width);
		dlgProperties->Edit6->Text = strWidth;
		// -----------------------------------------------------------------
		// DisplayCount
		iDisplayCount = Screen->DisplayCount;
		UnicodeString strDisplayCount(iDisplayCount);
		dlgProperties->Edit7->Text = strDisplayCount;

		// Desktop Width i pixels (might be for instance dual screen)
		iDesktopWidthInPixels = Screen->DesktopWidth;
		UnicodeString strDesktopWidth(iDesktopWidthInPixels);
		dlgProperties->Edit8->Text = strDesktopWidth;

		// Desktop Height i pixels
		iDesktopHeightInPixels = Screen->DesktopHeight;
		UnicodeString strDesktopHeight(iDesktopHeightInPixels);
		dlgProperties->Edit9->Text = strDesktopHeight;

		// Screen Width in pixels
		iScreenWidthInPixels = Screen->Width;
		UnicodeString strScreenWidth(iScreenWidthInPixels);
		dlgProperties->Edit10->Text = strScreenWidth;

		// Screen Height in pixels
		iScreenHeightInPixels = Screen->Height;
		UnicodeString strScreenHeight(iScreenHeightInPixels);
		dlgProperties->Edit11->Text = strScreenHeight;

		// Work area
		iWorkAreaWidthInPixels = Screen->WorkAreaWidth;
		UnicodeString strWorkAreaWidth(iWorkAreaWidthInPixels);
		dlgProperties->Edit12->Text = strWorkAreaWidth;

		iWorkAreaHeightInPixels = Screen->WorkAreaHeight;
		UnicodeString strWorkAreaHeight(iWorkAreaHeightInPixels);
		dlgProperties->Edit13->Text = strWorkAreaHeight;

		// Junior school math: a**2 + b**2 = c**2
		iScreenSizeInPixels = sqrt(iScreenWidthInPixels*iScreenWidthInPixels + iScreenHeightInPixels*iScreenHeightInPixels);
		UnicodeString strpixsize(iScreenSizeInPixels);
		dlgProperties->Edit14->Text = strpixsize;

        // Guessing 27" monitor as a starter
		iScreenPPI = iScreenSizeInPixels / 27;
		dlgProperties->Edit15->Text = iScreenPPI;

		//UnicodeString strii(17);

		dlgProperties->ComboBox1->Items->Add( 15);
		dlgProperties->ComboBox1->Items->Add( 17);
		dlgProperties->ComboBox1->Items->Add( 19);
		dlgProperties->ComboBox1->Items->Add( 20);
		dlgProperties->ComboBox1->Items->Add( 22);
		dlgProperties->ComboBox1->Items->Add( 24);
		dlgProperties->ComboBox1->Items->Add( 27);
		dlgProperties->ComboBox1->Items->Add( 28);
		dlgProperties->ComboBox1->Items->Add( 32);
		dlgProperties->ComboBox1->Items->Add( 34);
		dlgProperties->ComboBox1->Items->Add( 35);
		dlgProperties->ComboBox1->Items->Add( 43);
		dlgProperties->ComboBox1->Items->Add( 49);

		dlgProperties->ComboBox1->ItemIndex = 6;

		dlgProperties->ShowModal();
		if( dlgProperties -> iretState == 1 ) {

			//
		}

		dlgProperties->DisposeOf();
	}
	catch(...)
	{
		ShowMessage("Image properties did not succeed");
	}

}

Det som ligger av kode i selve dialogboksen, er å foreta en beregning hver gang du endrer skjermstørrelse. Da beregnes verdien av pixels/inch.

//---------------------------------------------------------------------------

void __fastcall TForm6::OnComboBoxChange(TObject *Sender)
{

	UnicodeString strSelected = ComboBox1->Selected->Text;
	int isize = strSelected.ToInt();
	UnicodeString scrSize = Edit14->Text;
	int iscrSize = scrSize.ToInt();
	int iPixPerInch = round(iscrSize / isize);

	Edit15->Text = iPixPerInch;
}

Dynamisk Meny

Jeg har valgt å generere alle menyene ved runtime, da har jeg full kontroll. Tidligere fiklet jeg litt med C++Builder’s egen internasjonalisering, men den slo ikke helt an hos meg. Jeg bruker i stedet en variant av noe jeg har laget og brukt tidligere.

Der ligger alle menyteksten i en egen fil, og har du behov for oversettelse til et annet språk, sender du bare denne filen til en translatør.

Slik systemet er her nå, lastes det en DLL og ferdig med det, det er greit nok, men det skulle ikke være alt for vanskelig å skrive om dette til et antall DLL-er og angi f.eks. i en ini-fil hvilken som skal lastes.

Systemet er som følger:

Det hele starter i OnFormCreate:

void __fastcall TForm1::OnFormCreate(TObject *Sender)
{
   ---
   ---
   hLibHandle = LoadLibrary( L"DynDllProject.dll" ); // Menu strings

   if( hLibHandle )
	{

	iret = CreateDynMenu( hLibHandle );
        ---
        ---

Ikke noe hokus-pokus i denne funksjonen altså. Alle menyfunksjonene er lagt i «menu1.cpp», du vil finne den kildefilen i prosjektlisten til høyre i RAD Studio.

Hovedfunksjonen, «CreateDynMenu», burde nok kanskje vært skrevet om litt, ingen vanskelighet det heller, men jeg bruker fortsatt gamle ting som jeg vet funker og for å slippe og tenke for mye 🙂 ( Jeg har utelatt detaljer vedr. testing for oversiktens skyld)

int  __fastcall TForm1::CreateDynMenu(HINSTANCE hLibHandle)
{
         ---	
         ---
	CreateFileMenu( hLibHandle );
	CreateEditMenu( hLibHandle );
	CreateSearchMenu( hLibHandle );
	CreateImageMenu( hLibHandle );
         ---
         ---
         ---

Vi kan da gå videre til funksjonene for hver enkelt meny. De er i prinsippet helt like, omtrent som følgende fra File-menyen:

//---------------------------------------------------------------------------
// ------------------------ CreateFileMenu ----------------------------------
//---------------------------------------------------------------------------

int  __fastcall TForm1::CreateFileMenu(HINSTANCE hLibHandle)
{
	TMenuItem *ParentItem;
	TMenuItem *ParentItem2;
	TMenuItem *ChildItem;

	if( hLibHandle != 0)
	{
		charReturnFunc myFunc;

		// GetProcAddress returns a pointer to the loaded method
		myFunc = (charReturnFunc)GetProcAddress(hLibHandle, "GetFileMenuString");

		if( myFunc != 0)
		{
			ParentItem = new TMenuItem(MenuBar1);
			ParentItem->Text = myFunc(0, 256);          // 0 = "File"
			ParentItem->Name = ParentItem->Text;
			MenuBar1->AddObject(ParentItem );

			ChildItem = new TMenuItem(ParentItem);
			ChildItem->Text = myFunc(1, 256);           // 1 = "New"
			ChildItem->Name = ChildItem->Text;
			ChildItem->OnClick = menu_FILE_NEW_Click;
			ParentItem->AddObject(ChildItem);

Først må du hente opp adressen til funksjonen (det er standard bruk av en DLL) og selve menyteksten hentes opp med «ParentItem->Text = myFunc(0, 256)» hvor første parameter er nummeret til tekststringen og det andre tallet er en maks-verdi.

Komplisert? egentlig ikke, men vi er ikke ferdig ennu. Vi skal videre se på de enkelte menyfunksjoner i DLL-en her vist ved parameter 2 i «myfunc», nemlig «GetFileMenuString».

///////////////////////////////////////////////////////////////////////
//
//   wchar_t* GetFileMenuString(UINT uID, int maxlen)
//
//
wchar_t* GetFileMenuString(UINT uID, int maxlen)
{

	switch (uID) {
		case 0:
			return menu_FILE.c_str();                   // index    0
		case 1:
			return menu_FILE_NEW.c_str();               //  1
		case 2:
			return menu_FILE_OPEN.c_str();              //  2
		case 3:
			return menu_FILE_OPEN_RECENT.c_str();       //  3

OK, nå gjenstår det vel bare selve tekststringene som alle ligger i samme fil:

///////////////////////////////////////////////////////////////////////////////
//
// Lots and lots of string defs
//
// FireMonkey does not like resourcefiles, so
// this is my first solution to that problem.
//
// $Rev: 19001 $
//
// Copyright(c) 2019- Gamlisen Norway
//              All rights reserved
//
// ------------------------------------------------------------------
//  File menu textstrings
//
const String menu_FILE = _T("File");                    	// index 	0
const String menu_FILE_NEW = _T("New");                 	//          1
const String menu_FILE_OPEN = _T("Open");               	//          2
const String menu_FILE_OPEN_RECENT = _T("OpenRecent"); 		//          3
const String menu_FILE_CLOSE = _T("CloseFile");        		//          4
const String menu_FILE_CLOSE_ALL = _T("CloseAll");     		//          5
const String menu_FILE_SAVE = _T("SaveFile");          		//          6
const String menu_FILE_SAVE_AS = _T("SaveFileAs");     		//          7
const String menu_FILE_SAVE_PROJECT = _T("SaveProject"); 	//        	8
const String menu_FILE_SAVE_PROJECT_AS = _T("SaveProjectAs"); //  		9

Nå er vi ferdig med alle de enkelte delene som inngår i min form for dynamisk meny.

Komplisert?

Egentlig ikke når du først har forstått systemet 🙂 Samtlige funksjoner blir forklart i detalj på de enkelte sidene.

FotoBox1 Layout

Nå får jeg muligens et lite problem, vil noen si. Det som står her i blogg-strømmen er jo omtrent det samme som finnes rundt omkring på sidene. Det får så være, men det som står her er alt mulig annet rusk og rask også, som f.eks. WordPress-tester (plugins). Jeg kommer sannsynligvis til å legge inn ideer og løsninger for bl.a Lightwave, men her i bloggstrømmen kommer alt helt ustrukturert etter hvert som jeg arbeider meg fremover.

Alt er skrevet i C++ og jeg bruker C++Builder fra RAD Studio (Embarcadero). Layout ved designtidspunkt ser slik ut:

Fotobox1 Layout

Jeg legger ut en TabControl, men foreløpig ingen Tab Items her, de lages ved runtime. Det har jeg gjort ved en ny klasse som er arvet fra TTabItem (jeg skal vise koden om litt).

Årsaken til dette er at den listboksen som vil forekomme på høyre side i programmet skal vise åpne objekter i hver enkelt tab. Jeg prøvde først å legge denne listboksen i hovedklassen, TForm1, men det ble litt komplisert ved at listboksen måtte cleares hver han man gikk inn i en ny tab med pekere frem og tilbake mellom TForm1 og dokumentklassen.

Bildet nedenfor viser hva jeg mener. Dette bildet er fra runtime hvor jeg har åpnet et bilde (av Gamlisen himself 🙂 ). På høyre side ser du her en listbox hvor navnet på åpne bilder settes inn samt visse taster for å skjule bildene, låse dem, osv. finnes.

Det er funksjoner som er tenkt implementert i neste versjon.

Alt dette lages ved File Open:

void __fastcall TForm1::menu_FILE_OPEN_Click(TObject *Sender)
{
	DocPage *NewItem;

	//Show Image files only
	OpenDialog1->Filter = TBitmapCodecManager::GetFilterString();

	if (OpenDialog1->Execute()) {

	try {

		// Add another Tab Item
		NewItem = new DocPage(TabControl1);
		NewItem->Parent = TabControl1;
		NewItem->Text = OpenDialog1->FileName;


		int maxTabs = TabControl1->TabCount;
		TabControl1->TabIndex = maxTabs-1;

		// Right frame containing Layer list, etc.
		NewItem->myFrame = new TFrame2(NewItem);
		NewItem->myFrame->Parent = NewItem;
		NewItem->myFrame->Width = 276;
		NewItem->myFrame->Align = TAlignLayout::Right;


		// 1. TFramedScrollBox
		NewItem->FramedScrollBox1 = new TFramedScrollBox(NewItem);
		NewItem->FramedScrollBox1->Parent = NewItem;
		NewItem->FramedScrollBox1->Align = TAlignLayout::Client;

		// 2. Layout1
		NewItem->Layout1 = new TLayout(NewItem->FramedScrollBox1);
		NewItem->Layout1->Parent = NewItem->FramedScrollBox1;
		NewItem->Layout1->Align = TAlignLayout::Center;

		// 3. Image1
		NewItem->Image1 = new TImage(NewItem->Layout1);
		NewItem->Image1->Parent = NewItem->Layout1;
		NewItem->Image1->Align =  TAlignLayout::Client;

		// Load From File into Image1
		NewItem->Image1->Bitmap->LoadFromFile(OpenDialog1->FileName);

		// Height and width
		NewItem->Layout1->Height = NewItem->Image1->Bitmap->Height;
		NewItem->Layout1->Width = NewItem->Image1->Bitmap->Width;


		// Add to Layer list
		TLayerItem *myLayerItem = new(TLayerItem);

		myLayerItem->FileName = new String (NewItem->Text);

		// Strip off short filename

		String Delimiters = _T("/\\");
		int ix = myLayerItem->FileName->LastDelimiter( Delimiters);
		ix += 1;

		// cheating a little, saying 30 characters as max length :-)
		myLayerItem->ShortName = new String(myLayerItem->FileName->SubString(ix, 30));

		myLayerItem->LayerName = myLayerItem->ShortName;
		myLayerItem->ElementType = File;
		myLayerItem->LayerFilter = 0;
		myLayerItem->Enabled = True;
		myLayerItem->LayerBitmap =  NewItem->Image1->Bitmap;

		NewItem->LayerList.push_back(myLayerItem);
		NewItem->myFrame->LayerListBox1->Items->Add(*myLayerItem->ShortName);
		NewItem->myFrame->TabControl2->ActiveTab = NewItem->myFrame->Layers;
		NewItem->myFrame->TabControl2->TabIndex = 0;
		//NewItem->myFrame->TabControl2->Tabs[2]->Enabled = False;

		//Enable Menu Items after Open File
		int iret = DisableEnableMenuItems(TRUE);

		}
	catch (...) {
		 ShowMessage(" Error from File Open");
		}

	}
}

Slik det er nå i denne versjonen, vil det lages en ny tab-side hver gang man åpner et bilde. Dette er selvfølgelig ikke godt nok i neste versjon. Da skal det være muligheter for flere åpne bilder i hver tab og kunne flytte disse frem og tilbake på siden f.eks.

Gamlisens Fotobox

Planen var først å lage et program for enkel bilderedigering, for så ved neste utgave å legge til et dokumentformat, dvs. tilsvarende et skrivebord, et A4 ark (dokument redigering).

Status Jan. 2020:
Fotobox 1 kan anses ferdig med bl.a. copy/paste, crop og filter funksjoner.

Fotobox 2 ikke påbegynt p.g.a misnøye med Firemonkeys grafikkbibliotek. Dette gjør om alle formater til 32 bits RGB internt. Det betyr at om du åpner ett eller annet bildeformat og så lagrer det tilbake, vil det lagres som 32 bits RGB uansett hva det var på forhånd.

Jeg begynner likevel å legge ut eksempler fra kodingen, så får tid og humør bestemme den videre utvikling.