Discussions related to Visual Prolog
Active Member
Posts: 47
Joined: 11 Jul 2002 23:01

Reading Metadata from a File

Unread post 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,
User avatar
Thomas Linder Puls
VIP Member
Posts: 1232
Joined: 28 Feb 2000 0:01

Re: Reading Metadata from a File

Unread post 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.
"Complete" demo project
(15.09 KiB) Downloaded 68 times
Regards Thomas Linder Puls
Active Member
Posts: 47
Joined: 11 Jul 2002 23:01

Re: Reading Metadata from a File

Unread post by B.Hooijenga »

Hello Thomas,

Thank you very much for your time and effort.

Everything works great and is very instructive.

Kind regards,

Post Reply