Posts: 6,350
Threads: 274
Joined: Oct 2016
Reputation:
565
Operating system(s):
Gimp version: 2.10
(02-10-2021, 02:13 AM)eepjr24 Wrote: (02-08-2021, 03:24 AM)ChameleonScales Wrote: Sure, what my plug-in does is from a given root directory and with a given maximum search depth, it finds all the xcfs and lists them in a csv table where each row is an xcf and each column is one of its layers (except the first column which is the url of the xcf).
Additionally, each "layer cell" of the csv contains the visibility state of the layer (whether the eye is open or closed) by inserting an identifiable string at the start of the cell.
Right now it works by using the Gimp api and I've started making a GTK dialog to make your "super-fast" version work outside of Gimp with the same functionalities.
I actually forgot to mention that "visibility" part, sorry about that (*∩▂∩). So right now I'm missing that from your version.
If it even is possible, do you perhaps know how to do that? (º̩̩́⌣º̩̩̀ʃƪ)
Consider my answer just a pointer in the right direction until tmanni or someone else who knows the xcf format can chime in. I think what you are looking for is:
PROP_VISIBLE (essential)
uint32 8 The type number for PROP_VISIBLE is 8
uint32 4 Four bytes of payload
uint32 b 1 if the layer/channel is visible; 0 if not
I pulled this from HERE, which is a partial XCF specification that will probably come in handy for the type of work you look to be doing. I only dabble in Python, so I'm not going to attempt to give a code example, but you are reading the layer name and the property list follows it immediately in the file:
string name The name of the layer
property-list Layer properties (details below)
That means from the name you should be able to seek N bytes forward in the file (where N is the sum of the bytes of the properties until the PROP_VISIBLE flag) and read / convert it (I think bitstring would be okay for this). Again, this is just to help you along in the mean time, I am no expert.
- E
Note tha there are currently two XCF formats, the up-to-2.8 one and the 2.10-onwards one, that supports (among other things) more channel blend modes and high-precision data. Not sure it matters here, but not sure it doesn't either...
Posts: 149
Threads: 2
Joined: Mar 2019
Reputation:
56
Operating system(s):
Gimp version: 3.0
IMO the most up to date xcf specification is here : https://gitlab.gnome.org/GNOME/gimp/-/bl...cs/xcf.txt
The gimp c code which open an xcf file and load it is here : https://gitlab.gnome.org/GNOME/gimp/-/bl...oad.c#L161
@ChameleonScales, do you read C language ? If the answer is yes, with a bit of work, you should be able to retrieve any information from a xcf file by completing the python script I posted.
Posts: 61
Threads: 16
Joined: Apr 2020
Reputation:
1
Operating system(s):
Gimp version: 2.10
02-11-2021, 06:22 PM
(This post was last modified: 02-11-2021, 06:24 PM by ChameleonScales.)
@Ofnuts: I'll focus on the most up-to-date stable version
@tmanni: Sorry, I have no knowledge in C and given the investment it represents, I don't have a plan to learn it atm.
I haven't even figured out how to complete your python script to include the layer visibility. bitstring reading is all new to me.
Posts: 6,350
Threads: 274
Joined: Oct 2016
Reputation:
565
Operating system(s):
Gimp version: 2.10
(02-11-2021, 06:22 PM)ChameleonScales Wrote: @Ofnuts: I'll focus on the most up-to-date stable version
@tmanni: Sorry, I have no knowledge in C and given the investment it represents, I don't have a plan to learn it atm.
I haven't even figured out how to complete your python script to include the layer visibility. bitstring reading is all new to me.
Reading C is easy, it's writing it which is complicated
Posts: 61
Threads: 16
Joined: Apr 2020
Reputation:
1
Operating system(s):
Gimp version: 2.10
Sorry to bother you again but I'm still clueless about how to print out the layer visibility from reading the binary data and that's the only thing missing to complete my plugin and publish it on gitlab.
Could anyone help?
Posts: 61
Threads: 16
Joined: Apr 2020
Reputation:
1
Operating system(s):
Gimp version: 2.10
Is there anyone I can get in touch with to help me on this? I'd be more than willing to pay for it, because I'm not learning C for a single small issue which I don't know where to begin solving. Maybe when I have a bigger project that requires C knowledge I'll get down to it, but right now this is the only thing I need in this language.
Posts: 6,350
Threads: 274
Joined: Oct 2016
Reputation:
565
Operating system(s):
Gimp version: 2.10
Posts: 61
Threads: 16
Joined: Apr 2020
Reputation:
1
Operating system(s):
Gimp version: 2.10
Thanks, I went on IRC and Akkana Peck (akk) solved it!
Here is the result (it also prints out which is the active layer as a bonus):
Code:
#!/usr/bin/env python3
import sys
PROP_ACTIVE_LAYER = 2
PROP_VISIBLE = 8
if __name__ == "__main__":
filename = sys.argv[1]
# open the file in readonly binary mode
with open(filename, 'rb') as f:
# go to the 30th bytes
f.seek(30, 0)
# read properties
while True:
prop_type = int.from_bytes(f.read(4), "big")
prop_size = int.from_bytes(f.read(4), "big")
f.read(prop_size)
if prop_type == 0: #PROP_END
break
# read layers
while True:
next_layer_offset = int.from_bytes(f.read(8), "big")
if not next_layer_offset: #end of layers offsets
break;
saved_pos = f.tell()
f.seek(next_layer_offset + 12, 0)
tmp = int.from_bytes(f.read(4), "big")
name = f.read(tmp).decode("utf-8")
print()
print(name)
while True:
prop_type = int.from_bytes(f.read(4), "big")
prop_size = int(int.from_bytes(f.read(4), "big") / 4)
# print(prop_type, "size", prop_size)
for i in range(prop_size):
lastint = int.from_bytes(f.read(4), "big")
if prop_type == PROP_VISIBLE:
print("Visible? %x" % lastint)
break
elif prop_type == PROP_ACTIVE_LAYER:
print("Active")
f.seek(saved_pos, 0)
Posts: 61
Threads: 16
Joined: Apr 2020
Reputation:
1
Operating system(s):
Gimp version: 2.10
09-21-2021, 08:52 PM
(This post was last modified: 09-21-2021, 08:54 PM by ChameleonScales.)
I found an issue with the previous code and got more help on IRC to fix it.
The issue was that the "name" variable sometimes included a terminating zero which messed with text editors when exporting the output to a file (and isn't part of the layer's name anyway).
We also made a few minor changes for better clarity :
Code:
#!/usr/bin/env python3
import sys
PROP_ACTIVE_LAYER = 2
PROP_VISIBLE = 8
if __name__ == "__main__":
filename = sys.argv[1]
# open the file in readonly binary mode
with open(filename, 'rb') as f:
# go to the 30th bytes
f.seek(30, 0)
# read properties
while True:
prop_type = int.from_bytes(f.read(4), "big")
prop_size = int.from_bytes(f.read(4), "big")
f.read(prop_size)
if prop_type == 0: #PROP_END
break
# read layers
while True:
next_layer_offset = int.from_bytes(f.read(8), "big")
if not next_layer_offset: #end of layers offsets
break;
saved_pos = f.tell()
f.seek(next_layer_offset + 12, 0)
name_len = int.from_bytes(f.read(4), "big")
name0 = f.read(name_len).decode("utf-8")
name = name0.replace('\0', '')
print()
print(name)
while True:
prop_type = int.from_bytes(f.read(4), "big")
prop_size = int(int.from_bytes(f.read(4), "big") / 4)
#print(prop_type, "size", prop_size)
for i in range(prop_size):
lastint = int.from_bytes(f.read(4), "big")
if prop_type == PROP_VISIBLE:
print("Visibility: %x" % lastint)
break
elif prop_type == PROP_ACTIVE_LAYER:
print("Active")
f.seek(saved_pos, 0)
Posts: 61
Threads: 16
Joined: Apr 2020
Reputation:
1
Operating system(s):
Gimp version: 2.10
10-02-2021, 10:58 AM
(This post was last modified: 10-02-2021, 12:30 PM by ChameleonScales.)
I found how to also print whether each layer contains a mask or not (by myself!).
Code:
#!/usr/bin/env python3
import sys
PROP_ACTIVE_LAYER = 2
PROP_VISIBLE = 8
if __name__ == "__main__":
filename = sys.argv[1]
# open the file in readonly binary mode
with open(filename, 'rb') as f:
# go to the 30th bytes
f.seek(30, 0)
# read properties
while True:
prop_type = int.from_bytes(f.read(4), "big")
prop_size = int.from_bytes(f.read(4), "big")
f.read(prop_size)
if prop_type == 0: #PROP_END
break
# read layers
while True:
next_layer_offset = int.from_bytes(f.read(8), "big")
if not next_layer_offset: #end of layers offsets
break;
saved_pos = f.tell()
f.seek(next_layer_offset + 12, 0)
name_len = int.from_bytes(f.read(4), "big")
name0 = f.read(name_len).decode("utf-8")
name = name0.replace('\0', '')
print()
print(name)
while True:
prop_type = int.from_bytes(f.read(4), "big")
prop_size = int(int.from_bytes(f.read(4), "big") / 4)
#print(prop_type, "size", prop_size)
for i in range(prop_size):
lastint = int.from_bytes(f.read(4), "big")
if prop_type == PROP_VISIBLE:
print("Visi: %x" % lastint)
elif prop_type == PROP_ACTIVE_LAYER:
print("Active")
elif prop_type == 0: #PROP_END
break
# hierarchy pointer:
hptr = int.from_bytes(f.read(8), "big")
# mask pointer:
mptr = int.from_bytes(f.read(8), "big")
if mptr == 0:
has_mask = 0
else:
has_mask = 1
print("Mask: " + str(has_mask))
f.seek(saved_pos, 0)
To recap on how to use this script in case anyone's lost, run it like this in the terminal:
Code:
./script.py "path/to/my.xcf"
|