Skip to content

Commit f388f92

Browse files
authored
Fix VBlob metadata loading (and therefore ICC profile loading) (#218)
1 parent 526ee19 commit f388f92

9 files changed

Lines changed: 80 additions & 44 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ repositories {
2323
}
2424

2525
dependencies {
26-
implementation("app.photofox.vips-ffm:vips-ffm-core:1.9.7")
26+
implementation("app.photofox.vips-ffm:vips-ffm-core:1.9.8")
2727
}
2828
```
2929

core/src/main/java/app/photofox/vipsffm/VBlob.java

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,11 @@ public final class VBlob {
3939
/// @param bytes The bytes to wrap
4040
public static VBlob newFromBytes(Arena arena, byte[] bytes) throws VipsError {
4141
var offHeapSegment = arena.allocateFrom(ValueLayout.JAVA_BYTE, bytes);
42-
var blobAddress = VipsRaw.vips_blob_new(MemorySegment.NULL, offHeapSegment, offHeapSegment.byteSize());
42+
return newFromDataSegment(arena, offHeapSegment);
43+
}
44+
45+
static VBlob newFromDataSegment(Arena arena, MemorySegment dataSegment) throws VipsError {
46+
var blobAddress = VipsRaw.vips_blob_new(MemorySegment.NULL, dataSegment, dataSegment.byteSize());
4347
if (!VipsValidation.isValidPointer(blobAddress)) {
4448
throw new VipsError("invalid blob returned from libvips");
4549
}
@@ -59,20 +63,20 @@ public MemorySegment getUnsafeStructAddress() throws VipsError {
5963
return this.address;
6064
}
6165

62-
/// Not recommended for use, use [#asByteBuffer()] instead
66+
/// Not recommended for use, use [#asArenaScopedByteBuffer()] or [#asClonedByteBuffer()] instead
6367
///
6468
/// Gets the raw [MemorySegment] (C pointer) for the data in this blob
6569
///
6670
/// Sliced to the length of the data, which isn't always null terminated
6771
public MemorySegment getUnsafeDataAddress() throws VipsError {
6872
var lengthOutPointer = arena.allocate(C_LONG);
69-
var dataPointer = VipsRaw.vips_area_get_data(
73+
var dataPointer = VipsRaw.vips_blob_get(
7074
this.address,
71-
lengthOutPointer,
72-
MemorySegment.NULL,
73-
MemorySegment.NULL,
74-
MemorySegment.NULL
75+
lengthOutPointer
7576
);
77+
if (!VipsValidation.isValidPointer(dataPointer)) {
78+
throw new VipsError("unexpected vblob bad data pointer");
79+
}
7680
var length = lengthOutPointer.get(C_LONG, 0);
7781
if (length < 0) {
7882
throw new VipsError("unexpected length of vblob data " + length);
@@ -89,6 +93,10 @@ public long byteSize() {
8993
///
9094
/// Mapped to native memory via DirectByteBuffer, hence does not make a copy, so the data has the
9195
/// same data lifetime as [#arena]
96+
///
97+
/// Note that because this is backed by native memory, you cannot use the [ByteBuffer#array()]
98+
/// method on the resulting buffer. Use [#asClonedByteBuffer()] or [#getBytes()] if you're
99+
/// looking for a full copy of the data.
92100
public ByteBuffer asArenaScopedByteBuffer() {
93101
return this.getUnsafeDataAddress().asByteBuffer();
94102
}
@@ -107,4 +115,13 @@ public ByteBuffer asClonedByteBuffer() {
107115
newBuffer.rewind();
108116
return newBuffer;
109117
}
118+
119+
/// byte[] representation of the data in this VipsBlob
120+
///
121+
/// Useful if you're working with metadata stored on an image, like an ICC profile
122+
///
123+
/// Note that a full copy of the data is taken
124+
public byte[] getBytes() {
125+
return this.getUnsafeDataAddress().toArray(ValueLayout.JAVA_BYTE);
126+
}
110127
}

core/src/main/java/app/photofox/vipsffm/VImage.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10465,15 +10465,15 @@ public VBlob getBlob(String name) {
1046510465
if (blobLength <= 0) {
1046610466
throw new VipsError("failed to read length of type blob from field: " + name);
1046710467
}
10468-
var blobAddress = outPointer.get(VipsRaw.C_POINTER, 0).reinterpret(blobLength);
10469-
return new VBlob(arena, blobAddress);
10468+
var dataSegment = outPointer.get(VipsRaw.C_POINTER, 0).reinterpret(blobLength);
10469+
return VBlob.newFromDataSegment(arena, dataSegment);
1047010470
}
1047110471

1047210472
/// Helper function to set the metadata stored at `name` on this image, of type `blob`
1047310473
///
1047410474
/// See also: [libvips header docs](https://www.libvips.org/API/current/libvips-header.html)
1047510475
public VImage set(String name, VBlob value) {
10476-
VipsHelper.image_set_blob(arena, this.address, name, MemorySegment.NULL, value.address, value.byteSize());
10476+
VipsHelper.image_set_blob(arena, this.address, name, MemorySegment.NULL, value.getUnsafeDataAddress(), value.byteSize());
1047710477
return this;
1047810478
}
1047910479

docs/app.photofox.vipsffm/app/photofox/vipsffm/VBlob.html

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
<li><a href="#byteSize()" tabindex="0">byteSize()</a></li>
7272
<li><a href="#asArenaScopedByteBuffer()" tabindex="0">asArenaScopedByteBuffer()</a></li>
7373
<li><a href="#asClonedByteBuffer()" tabindex="0">asClonedByteBuffer()</a></li>
74+
<li><a href="#getBytes()" tabindex="0">getBytes()</a></li>
7475
</ol>
7576
</li>
7677
</ol>
@@ -122,26 +123,25 @@ <h2>Method Summary</h2>
122123
<div class="col-last even-row-color method-summary-table method-summary-table-tab2 method-summary-table-tab4">
123124
<div class="block">Size of the data in this blob</div>
124125
</div>
125-
<div class="col-first odd-row-color method-summary-table method-summary-table-tab2 method-summary-table-tab4"><code><a href="https://docs.oracle.com/en/java/javase/23/docs/api/java.base/java/lang/foreign/MemorySegment.html" title="class or interface in java.lang.foreign" class="external-link">MemorySegment</a></code></div>
126-
<div class="col-second odd-row-color method-summary-table method-summary-table-tab2 method-summary-table-tab4"><code><a href="#getUnsafeDataAddress()" class="member-name-link">getUnsafeDataAddress</a>()</code></div>
126+
<div class="col-first odd-row-color method-summary-table method-summary-table-tab2 method-summary-table-tab4"><code>byte[]</code></div>
127+
<div class="col-second odd-row-color method-summary-table method-summary-table-tab2 method-summary-table-tab4"><code><a href="#getBytes()" class="member-name-link">getBytes</a>()</code></div>
127128
<div class="col-last odd-row-color method-summary-table method-summary-table-tab2 method-summary-table-tab4">
128-
<div class="block"><p>Not recommended for use, use</p>
129-
<details class="invalid-tag">
130-
<summary>invalid reference</summary>
131-
<pre><code>#asByteBuffer()</code></pre>
132-
</details>
133-
instead
134-
</div>
129+
<div class="block">byte[] representation of the data in this VipsBlob</div>
135130
</div>
136131
<div class="col-first even-row-color method-summary-table method-summary-table-tab2 method-summary-table-tab4"><code><a href="https://docs.oracle.com/en/java/javase/23/docs/api/java.base/java/lang/foreign/MemorySegment.html" title="class or interface in java.lang.foreign" class="external-link">MemorySegment</a></code></div>
137-
<div class="col-second even-row-color method-summary-table method-summary-table-tab2 method-summary-table-tab4"><code><a href="#getUnsafeStructAddress()" class="member-name-link">getUnsafeStructAddress</a>()</code></div>
132+
<div class="col-second even-row-color method-summary-table method-summary-table-tab2 method-summary-table-tab4"><code><a href="#getUnsafeDataAddress()" class="member-name-link">getUnsafeDataAddress</a>()</code></div>
138133
<div class="col-last even-row-color method-summary-table method-summary-table-tab2 method-summary-table-tab4">
134+
<div class="block">Not recommended for use, use <a href="#asArenaScopedByteBuffer()"><code>asArenaScopedByteBuffer()</code></a> or <a href="#asClonedByteBuffer()"><code>asClonedByteBuffer()</code></a> instead</div>
135+
</div>
136+
<div class="col-first odd-row-color method-summary-table method-summary-table-tab2 method-summary-table-tab4"><code><a href="https://docs.oracle.com/en/java/javase/23/docs/api/java.base/java/lang/foreign/MemorySegment.html" title="class or interface in java.lang.foreign" class="external-link">MemorySegment</a></code></div>
137+
<div class="col-second odd-row-color method-summary-table method-summary-table-tab2 method-summary-table-tab4"><code><a href="#getUnsafeStructAddress()" class="member-name-link">getUnsafeStructAddress</a>()</code></div>
138+
<div class="col-last odd-row-color method-summary-table method-summary-table-tab2 method-summary-table-tab4">
139139
<div class="block">Gets the raw <a href="https://docs.oracle.com/en/java/javase/23/docs/api/java.base/java/lang/foreign/MemorySegment.html" title="class or interface in java.lang.foreign" class="external-link"><code>MemorySegment</code></a> (C pointer) for this VipsBlob struct</div>
140140
</div>
141-
<div class="col-first odd-row-color method-summary-table method-summary-table-tab1 method-summary-table-tab4"><code>static <a href="VBlob.html" title="class in app.photofox.vipsffm">VBlob</a></code></div>
142-
<div class="col-second odd-row-color method-summary-table method-summary-table-tab1 method-summary-table-tab4"><code><a href="#newFromBytes(java.lang.foreign.Arena,byte%5B%5D)" class="member-name-link">newFromBytes</a><wbr>(<a href="https://docs.oracle.com/en/java/javase/23/docs/api/java.base/java/lang/foreign/Arena.html" title="class or interface in java.lang.foreign" class="external-link">Arena</a>&nbsp;arena,
141+
<div class="col-first even-row-color method-summary-table method-summary-table-tab1 method-summary-table-tab4"><code>static <a href="VBlob.html" title="class in app.photofox.vipsffm">VBlob</a></code></div>
142+
<div class="col-second even-row-color method-summary-table method-summary-table-tab1 method-summary-table-tab4"><code><a href="#newFromBytes(java.lang.foreign.Arena,byte%5B%5D)" class="member-name-link">newFromBytes</a><wbr>(<a href="https://docs.oracle.com/en/java/javase/23/docs/api/java.base/java/lang/foreign/Arena.html" title="class or interface in java.lang.foreign" class="external-link">Arena</a>&nbsp;arena,
143143
byte[]&nbsp;bytes)</code></div>
144-
<div class="col-last odd-row-color method-summary-table method-summary-table-tab1 method-summary-table-tab4">
144+
<div class="col-last even-row-color method-summary-table method-summary-table-tab1 method-summary-table-tab4">
145145
<div class="block">Creates a new VBlob from a fixed array of bytes</div>
146146
</div>
147147
</div>
@@ -206,12 +206,7 @@ <h3>getUnsafeDataAddress</h3>
206206
<div class="horizontal-scroll">
207207
<div class="member-signature"><span class="modifiers">public</span>&nbsp;<span class="return-type"><a href="https://docs.oracle.com/en/java/javase/23/docs/api/java.base/java/lang/foreign/MemorySegment.html" title="class or interface in java.lang.foreign" class="external-link">MemorySegment</a></span>&nbsp;<span class="element-name">getUnsafeDataAddress</span>()
208208
throws <span class="exceptions"><a href="VipsError.html" title="class in app.photofox.vipsffm">VipsError</a></span></div>
209-
<div class="block"><p>Not recommended for use, use</p>
210-
<details class="invalid-tag">
211-
<summary>invalid reference</summary>
212-
<pre><code>#asByteBuffer()</code></pre>
213-
</details>
214-
instead
209+
<div class="block"><p>Not recommended for use, use <a href="#asArenaScopedByteBuffer()"><code>asArenaScopedByteBuffer()</code></a> or <a href="#asClonedByteBuffer()"><code>asClonedByteBuffer()</code></a> instead</p>
215210
<p>Gets the raw <a href="https://docs.oracle.com/en/java/javase/23/docs/api/java.base/java/lang/foreign/MemorySegment.html" title="class or interface in java.lang.foreign" class="external-link"><code>MemorySegment</code></a> (C pointer) for the data in this blob</p>
216211
<p>Sliced to the length of the data, which isn't always null terminated</p>
217212
</div>
@@ -239,6 +234,9 @@ <h3>asArenaScopedByteBuffer</h3>
239234
<div class="block"><p>ByteBuffer representation of the data in this blob</p>
240235
<p>Mapped to native memory via DirectByteBuffer, hence does not make a copy, so the data has the
241236
same data lifetime as <code>arena</code></p>
237+
<p>Note that because this is backed by native memory, you cannot use the <a href="https://docs.oracle.com/en/java/javase/23/docs/api/java.base/java/nio/ByteBuffer.html#array()" title="class or interface in java.nio" class="external-link"><code>ByteBuffer.array()</code></a>
238+
method on the resulting buffer. Use <a href="#asClonedByteBuffer()"><code>asClonedByteBuffer()</code></a> or <a href="#getBytes()"><code>getBytes()</code></a> if you're
239+
looking for a full copy of the data.</p>
242240
</div>
243241
</div>
244242
</section>
@@ -254,6 +252,18 @@ <h3>asClonedByteBuffer</h3>
254252
</div>
255253
</section>
256254
</li>
255+
<li>
256+
<section class="detail" id="getBytes()">
257+
<h3>getBytes</h3>
258+
<div class="horizontal-scroll">
259+
<div class="member-signature"><span class="modifiers">public</span>&nbsp;<span class="return-type">byte[]</span>&nbsp;<span class="element-name">getBytes</span>()</div>
260+
<div class="block"><p>byte[] representation of the data in this VipsBlob</p>
261+
<p>Useful if you're working with metadata stored on an image, like an ICC profile</p>
262+
<p>Note that a full copy of the data is taken</p>
263+
</div>
264+
</div>
265+
</section>
266+
</li>
257267
</ul>
258268
</section>
259269
</li>

docs/index-all.html

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5397,6 +5397,10 @@ <h2 class="title" id="I:G">G</h2>
53975397
<div class="block">Helper function to get the metadata stored at <code>name</code> on this image, of type <code>blob</code>, or <code>null</code>
53985398
if not present</div>
53995399
</dd>
5400+
<dt><a href="app.photofox.vipsffm/app/photofox/vipsffm/VBlob.html#getBytes()" class="member-name-link">getBytes()</a> - Method in class app.photofox.vipsffm.<a href="app.photofox.vipsffm/app/photofox/vipsffm/VBlob.html" title="class in app.photofox.vipsffm">VBlob</a></dt>
5401+
<dd>
5402+
<div class="block">byte[] representation of the data in this VipsBlob</div>
5403+
</dd>
54005404
<dt><a href="app.photofox.vipsffm/app/photofox/vipsffm/VImage.html#getDouble(java.lang.String)" class="member-name-link">getDouble(String)</a> - Method in class app.photofox.vipsffm.<a href="app.photofox.vipsffm/app/photofox/vipsffm/VImage.html" title="class in app.photofox.vipsffm">VImage</a></dt>
54015405
<dd>
54025406
<div class="block">Helper function to get the metadata stored at <code>name</code> on this image, of type <code>double</code>, or <code>null</code>
@@ -5701,13 +5705,7 @@ <h2 class="title" id="I:G">G</h2>
57015705
</dd>
57025706
<dt><a href="app.photofox.vipsffm/app/photofox/vipsffm/VBlob.html#getUnsafeDataAddress()" class="member-name-link">getUnsafeDataAddress()</a> - Method in class app.photofox.vipsffm.<a href="app.photofox.vipsffm/app/photofox/vipsffm/VBlob.html" title="class in app.photofox.vipsffm">VBlob</a></dt>
57035707
<dd>
5704-
<div class="block"><p>Not recommended for use, use</p>
5705-
<details class="invalid-tag">
5706-
<summary>invalid reference</summary>
5707-
<pre><code>#asByteBuffer()</code></pre>
5708-
</details>
5709-
instead
5710-
</div>
5708+
<div class="block">Not recommended for use, use <a href="app.photofox.vipsffm/app/photofox/vipsffm/VBlob.html#asArenaScopedByteBuffer()"><code>VBlob.asArenaScopedByteBuffer()</code></a> or <a href="app.photofox.vipsffm/app/photofox/vipsffm/VBlob.html#asClonedByteBuffer()"><code>VBlob.asClonedByteBuffer()</code></a> instead</div>
57115709
</dd>
57125710
<dt><a href="app.photofox.vipsffm/app/photofox/vipsffm/VBlob.html#getUnsafeStructAddress()" class="member-name-link">getUnsafeStructAddress()</a> - Method in class app.photofox.vipsffm.<a href="app.photofox.vipsffm/app/photofox/vipsffm/VBlob.html" title="class in app.photofox.vipsffm">VBlob</a></dt>
57135711
<dd>

docs/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ <h2 id="usage-heading">Usage</h2>
7070
}
7171

7272
dependencies {
73-
implementation(&quot;app.photofox.vips-ffm:vips-ffm-core:1.9.7&quot;)
73+
implementation(&quot;app.photofox.vips-ffm:vips-ffm-core:1.9.8&quot;)
7474
}
7575
</code></pre>
7676
<p>Figure out what you're trying to do by looking at the <a href="https://www.libvips.org/API/current/">libvips documentation</a>

docs/member-search-index.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

generator/src/main/kotlin/vipsffm/GenerateVClasses.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -968,7 +968,7 @@ object GenerateVClasses {
968968
when (poetValueType) {
969969
vblobType -> {
970970
this.addStatement(
971-
"\$T.image_set_$typeName(arena, this.address, name, \$T.NULL, value.address, value.byteSize())",
971+
"\$T.image_set_$typeName(arena, this.address, name, \$T.NULL, value.getUnsafeDataAddress(), value.byteSize())",
972972
vipsHelperType,
973973
memorySegmentType
974974
)
@@ -1087,8 +1087,8 @@ object GenerateVClasses {
10871087
.endControlFlow()
10881088
.build()
10891089
)
1090-
this.addStatement("var blobAddress = outPointer.get(\$T.C_POINTER, 0).reinterpret(blobLength)", vipsRawType)
1091-
this.addStatement("return new VBlob(arena, blobAddress)")
1090+
this.addStatement("var dataSegment = outPointer.get(\$T.C_POINTER, 0).reinterpret(blobLength)", vipsRawType)
1091+
this.addStatement("return VBlob.newFromDataSegment(arena, dataSegment)")
10921092
}
10931093

10941094
vimageType -> {

sample/src/main/kotlin/vipsffm/sample/VImageGetSetSample.kt

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import app.photofox.vipsffm.VBlob
44
import app.photofox.vipsffm.VImage
55
import org.slf4j.LoggerFactory
66
import vipsffm.RunnableSample
7+
import java.awt.color.ColorSpace
8+
import java.awt.color.ICC_Profile
79
import java.lang.foreign.Arena
10+
import java.lang.foreign.ValueLayout
811
import java.nio.file.Path
912

1013
/**
@@ -46,13 +49,21 @@ object VImageGetSetSample: RunnableSample {
4649
)
4750
}
4851

49-
val bytes = sourceImage.getBlob("test-name-blob").asClonedByteBuffer()
50-
if (!bytes.array().contentEquals(byteArrayOf(0x01, 0x02, 0x03, 0x04))) {
52+
val bytes = sourceImage.getBlob("test-name-blob").bytes
53+
if (!bytes.contentEquals(byteArrayOf(0x01, 0x02, 0x03, 0x04))) {
5154
return Result.failure(
5255
RuntimeException("unexpected value in metadata")
5356
)
5457
}
5558

59+
val iccBytes = sourceImage.getBlob("icc-profile-data").asClonedByteBuffer()
60+
val profile = ICC_Profile.getInstance(iccBytes.array())
61+
if (profile.majorVersion != 2 || profile.colorSpaceType != ColorSpace.TYPE_RGB) {
62+
return Result.failure(
63+
RuntimeException("unexpected icc profile values")
64+
)
65+
}
66+
5667
val imageFromMetadata = sourceImage.getImage("test-name-image")
5768
if (imageFromMetadata.width != 100 && imageFromMetadata.height != 100) {
5869
return Result.failure(

0 commit comments

Comments
 (0)