Page 1 of 1

Reading Metadata from a File

Posted: 18 Feb 2021 12:01
by B.Hooijenga
Hello Thomas,

I am in the process of studying GDI+.
I am trying to read the metadata of a file, using this link as an example:
https://docs.microsoft.com/en-us/window ... tadata-use

This is my code so far.

Code: Select all

doDraw(_Graphics, "Reading and Writing Metadata") :-         Image = image::createFromFile(@"vlinder.jpg"),         Image:getPropertySize(TotalBufferSize, NumberOfProperties),         L = Image:getAllPropertyItems(TotalBufferSize, NumberOfProperties),         foreach ItemId in L do             stdio::write("ImagepropertyId =  ", ItemId, "\n")         end foreach,         %         foreach ItemId in L do             ItemId = gdiplus::propertyItem(PropId, VarArraySize, Type, VarArray),             stdio::write("\nPropId = ", PropId, "\nVarArraySize = ", VarArraySize, "\nType = ", Type, " = "),             stdio::write("\n", help(Type), "\nVarArray = ", VarArray, "\n")         end foreach,         !.
I get this result, which looks good.

ImagepropertyId = propertyItem(315,4,2,0268A940)
ImagepropertyId = propertyItem(36867,20,2,0268A944)
ImagepropertyId = propertyItem(36868,20,2,0268A958)
ImagepropertyId = propertyItem(37521,3,2,0268A96C)
ImagepropertyId = propertyItem(37522,3,2,0268A96F)
ImagepropertyId = propertyItem(40093,8,1,0268A972)
ImagepropertyId = propertyItem(20625,128,3,0268A97A)
ImagepropertyId = propertyItem(20624,128,3,0268A9FA)

Slightly clearer (I have omitted redundant items).

PropId = 315
VarArraySize = 4
Type = 2 = propertyTagTypeASCII
VarArray = 0264A940
......
......
PropId = 40093
VarArraySize = 8
Type = 1 = propertyTagTypeByte
VarArray = 0264A972

PropId = 20625
VarArraySize = 128
Type = 3 = propertyTagTypeShort
VarArray = 0264A97A

I understand that there is an array for every propertyitem.
From each array we know the arraypointer, the arraysize and the type of the arrayvalue ( the propertytagtype).
My question is : how can I read each array?

Kind regards,
Ben

Re: Reading Metadata from a File

Posted: 19 Feb 2021 20:16
by Thomas Linder Puls
The interface you get there is quite low level, mapping rather directly to GDI+ (i.e. a C++ interface).

If we had more time we could clearly "raise" the interface to a higher level.

Let us start by considering what kinds of data we are dealing with, by considering a couple of the properties.

The first property 315 (= 0x13B) is
  • PropertyTagArtist
  • Null-terminated character string that specifies the name of the person who created the image.
  • Tag: 0x013B
  • Type: PropertyTagTypeASCII
  • Count: Length of the string including the NULL terminator
So for PropertyTagTypeASCII we are dealing with a string8 value.

Property 40093 (= 0x9C9D) is not in that list, but the principle will follow the one for 20626 (= 0x5091):
  • PropertyTagArtist
  • Chrominance table. The luminance table and the chrominance table are used to control JPEG quality. A valid luminance or chrominance table has 64 entries of type PropertyTagTypeShort. If an image has either a luminance table or a chrominance table, then it must have both tables.
  • Tag: 0x5091
  • Type: PropertyTagTypeShort
  • Count: 64
So here we are dealing with an array consisting of 64 shorts; which corresponds to core::unsigned16. 64 16 bit unsigned is consistent with the byte size 128 that you get.

Lets us take one more:
  • PropertyTagGpsLongitude
  • Longitude. Longitude is expressed as three rational values giving the degrees, minutes, and seconds respectively. When degrees, minutes and seconds are expressed, the format is ddd/1, mm/1, ss/1. When degrees and minutes are used and, for example, fractions of minutes are given up to two decimal places, the format is ddd/1, mmmm/100, 0/1.
  • Tag: 0x0004
  • Type: PropertyTagTypeRational
  • Count: 3
So each rational consists of two unsigned which are the nominator and denominator of a fraction. For a GPS Longitude there are three of them. So the byte size in this case will be 3 (rationals) * 2 (unsigned) * 4 bytes = 24 Bytes.

There is a little "twist" in those cases where there is one value in the array. For example in
  • PropertyTagGpsSpeed
  • Speed of the GPS receiver movement.
  • Tag: 0x000D
  • Type: PropertyTagTypeRational
  • Count: 1
In such cases it is not really an array with one value, it is just the value.

The PropertyTagTypeByte is most likely not really a lot of numbers, but just "some data" (a thumbnail for example), but if there is only one then it is quitelikely a number.

The PropertyTagTypeUndefined is in any case just "some data", even when there is only one byte.

All in all however there ar quite a number of different kinds of data.

And the first question is: What do you what to have in Prolog?

If you want to represent it as genuine Prolog data then you should have a functor domain covering all the different cases.

Code: Select all

domains     propertyValue =             byte(unsigned8 Value);                 byteArray(binary Value);                 string8(string8 Value);                 rational(tuple{unsigned Nominator, unsigned Denominator});                 rationalArray(tuple{unsigned Nominator, unsigned Denominator}* List);                 rationlS(tuple{integer Nominator, integer Denominator});                 ...
There are many choices to make:
  • I have chosen that arrays should be represented as lists.
  • I have chosen to have separate "one value" cases, but they could also just be lists with one element.
  • ...
It is somewhat simpler to convert data to strings. Because a string can represent any kind of data, so in that case we don't need all the mentioned functors etc.

Here is an example of that:

Code: Select all

class predicates     writeProperties : (string ImageFile). clauses     writeProperties(ImageFile) :-         Image = image::createFromFile(ImageFile),         Image:getPropertySize(TotalBufferSize, NumberOfProperties),         ALL = Image:getAllPropertyItems(TotalBufferSize, NumberOfProperties),         foreach propertyItem(Id, Size, Type, Pointer) in ALL do             writef("%: %\n", gdiplus::getPropertyName(Id), getValueString(Size, Type, Pointer))         end foreach.   domains     fraction{Type} =         fraction(Type Nominator, Type Denominator)         [presenter(presenter_fraction)].   class predicates     getValueString : (byteCount Size, propertyTagType Type, pointer Pointer) -> string ValueString. clauses     getValueString(_Size, propertyTagTypeASCII, Pointer) = ValueString :-         !,         ValueString = string::format("%", uncheckedConvert(string8, Pointer)).       getValueString(1, propertyTagTypeByte, Pointer) = ValueString :-         !,         ValueString = getValueString_value(1, uncheckedConvert(memory::pointerTo{unsigned8}, Pointer)).       getValueString(Size, propertyTagTypeShort, Pointer) = ValueString :-         !,         ValueString = getValueString_value(Size, uncheckedConvert(memory::pointerTo{unsigned16}, Pointer)).       getValueString(Size, propertyTagTypeLong, Pointer) = ValueString :-         !,         ValueString = getValueString_value(Size, uncheckedConvert(memory::pointerTo{unsigned}, Pointer)).       getValueString(Size, propertyTagTypeSLong, Pointer) = ValueString :-         !,         ValueString = getValueString_value(Size, uncheckedConvert(memory::pointerTo{integer}, Pointer)).       getValueString(Size, propertyTagTypeRational, Pointer) = ValueString :-         !,         ValueString = getValueString_struct(Size, uncheckedConvert(fraction{unsigned}, Pointer)).       getValueString(Size, propertyTagTypeSRational, Pointer) = ValueString :-         !,         ValueString = getValueString_struct(Size, uncheckedConvert(fraction{integer}, Pointer)).       getValueString(Size, Type, Pointer) = string::format("% (%)", binary::createAtomicFromPointer(Pointer, Size), Type).   class predicates     getValueString_value : (byteCount Size, memory::pointerTo{Type} Pointer) -> string ValueString. clauses     getValueString_value(Size, PointerTo) = list_string(buildList_value(Size, PointerTo)).   class predicates     getValueString_struct : (byteCount Size, Struct Pointer) -> string ValueString. clauses     getValueString_struct(Size, Pointer) = list_string(buildList_struct(Size, Pointer)).   class predicates     list_string : (Type* List) -> string ValueString. clauses     list_string([Elem]) = string::present(Elem) :-         !.     list_string(List) = string::present(List).   class predicates     buildList_value : (byteCount Size, memory::pointerTo{Type} PointerTo) -> Type* List. clauses     buildList_value(0, _PointerTo) = [] :-         !.     buildList_value(Size, PointerTo) = [V | VL] :-         V = memory::get(PointerTo),         SizeElem = sizeof(V),         Next = memory::uncheckedFromPointer(memory::pointerAddLite(uncheckedConvert(pointer, PointerTo), SizeElem)),         VL = buildList_value(Size - SizeElem, Next).   class predicates     buildList_struct : (byteCount Size, Struct Pointer) -> Struct* List. clauses     buildList_struct(0, _Pointer) = [] :-         !.     buildList_struct(Size, Pointer) = [V | VL] :-         V = Pointer,         SizeElem = sizeof(V),         Next = memory::uncheckedFromPointer(memory::pointerAddLite(uncheckedConvert(pointer, Pointer), SizeElem)),         VL = buildList_struct(Size - SizeElem, Next).   class predicates     presenter_fraction : presenter::presenter{fraction{Type}}. clauses     presenter_fraction(fraction(Nominator, Denominator)) =         presenter::mkPresenter_fixed(presenter::mkFP_term("Nominator", Nominator), presenter::fp_string("/"),             presenter::mkFP_term("Denominator", Denominator)).
The code contains some pointer arithmetic and memory manipulation, which require "deep" understanding of representation.

I have added a complete project which also contains an updade of the gdiplus class, notably the predicate getPropertyName which makes it all much nicer.

Re: Reading Metadata from a File

Posted: 22 Feb 2021 12:41
by B.Hooijenga
Hello Thomas,

Thank you very much for your time and effort.

Everything works great and is very instructive.

Kind regards,

Ben