Commit Graph
106 Commits
Author SHA1 Message Date
FredApps fd3a12dfde Merge approved PhotoCraft PR #3142: Read applied Sony RAW metadata and crop ILCE-6000 images
Port behavior and regression coverage into current modules; preserve current upstream behavior.
Integrate bounded Sony SR2 metadata decoding and ILCE-6000 image area; preserve newer bit-depth-aware camera profile application and explicit file crop precedence. Keep current parity counts and original metadata/crop tests.
2026-10-29 06:38:44 +01:00
FredApps 71afe098ca Merge approved PhotoCraft PR #9457: raw: faster tone stage (Shadows, Highlights, Clarity, Texture, Dehaze), same bytes 2026-10-29 03:29:53 +01:00
Dylan M. Taylor 52800a7b3d Refactor raw for strict Clippy checks 2026-10-27 23:47:06 +00:00
David Mann 77f7914271 Merge PR #9455: raw: develop the default rendering band by band, without the f32 RGB frame 2026-10-27 14:28:29 +01:00
David Mann 3a689f217c Merge remote-tracking branch 'origin/main' into HEAD
# Conflicts:
#	crates/engine/src/file_cmds.rs
#	crates/engine/src/retouch_cmds.rs
#	crates/engine/src/vector_cmds.rs
#	crates/io/src/psd_export.rs
#	crates/io/src/psd_import.rs
#	crates/io/src/smart_map.rs
#	crates/psd/src/layer.rs
#	crates/raster/src/lib.rs
#	crates/ui-egui/src/camera_raw_ui.rs
#	crates/ui-egui/src/canvas.rs
#	crates/ui-egui/src/filter_dialog.rs
#	crates/ui-egui/src/gpu_canvas.rs
#	crates/ui-egui/src/panels.rs
#	crates/ui-egui/src/retouch_ui.rs
#	crates/ui-egui/src/transform_tool.rs
#	crates/ui-egui/src/vector_ui.rs
#	docs/gaps.md
2026-10-27 14:26:26 +01:00
0b09eb43a5 Integration: per-band AHD balancing, memory-capped fused bands (#9455)
`for_bands_grouped` always split the bands into `min(workers, jobs)` static
chunks, so on the default develop (jobs == workers) a worker held up on the
shared pool kept a fixed share of the bands waiting, where main's
`chunks_mut_limited` runs one rayon task per band. Now, when every worker may
hold a scratch, each band is its own task and borrows a scratch from a pool
(`par::chunks_mut_pooled`, at most one per worker, dropped as soon as no
waiting band can use it); the grouping stays for memory-limited jobs.

The fused bilinear/MHC, X-Trans and LinearRaw paths ran one band per worker
with no memory cap: a 262144 x 455 Bayer frame with bilinear needed 2.92 GB
at 15+ workers and failed with LimitExceeded under the default 2 GiB.
`working_memory` now caps those bands like AHD's and the fused paths use the
returned jobs. The fused LinearRaw and X-Trans band buffers are `band.min(h)`
rows, as charged (CodeRabbit, develop.rs:414).

Tests: a_slow_band_does_not_hold_up_the_bands_after_it and
fused_bands_at_once_are_capped_by_the_memory_limit fail without the fix;
for_bands_keeps_at_most_jobs_scratches_alive, chunks_mut_pooled_makes_one_state_per_worker_at_most,
multi-band and short X-Trans/LinearRaw fused-vs-full cases.

Co-authored-by: Valentin Klein <271991477+vangel76@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 14:24:57 +01:00
aiongg 85539a4f37 refactor(raw): split testgen.rs into testgen/ 2026-10-26 22:54:56 +00:00
aiongg a50edcebb0 refactor(raw): move nefc.rs tests to nefc/tests.rs 2026-10-26 22:30:14 +00:00
vangel76andClaude Sonnet 5.5 3e144a381a Merge upstream/main into perf/9434-fused-develop
Main reverted the #9455 merge, so a plain merge would drop the band-by-band
develop; keep this PR's demosaic.rs, develop.rs and development_limits.rs and
take all other upstream changes.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 01:18:21 +01:00
vangel76andClaude Sonnet 5.5 1e52abfb28 raw: restore the LabF pair table that the main revert dropped from the merge
Main reverted the earlier #9455 merge, which also removed the AHD changes
that came in with it; keep this PR's AHD code and add clampf plus the
NaN-plane test from main.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 01:15:47 +01:00
vangel76andClaude Sonnet 5.5 0dcc17770e Merge upstream/main into perf/9427-ahd-demosaic
Keep the AHD scratch/band changes; take clampf and the NaN-plane test from main
(main reverted the earlier #9455 merge, so the AHD changes stay on this branch).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 01:14:30 +01:00
aiongg cece50d513 Merge pull request #9386 from davidmann4/integration-batch-8
Integration batch 8: 37 reviewed community PRs on main
2026-10-25 20:04:41 -04:00
Dylan M. Taylor 3877bb1dbe chore: resolve SonarCloud maintainability issues (#9044)
* chore: resolve SonarCloud maintainability issues

* fix: format imports and fix effect_entry argument type

* fix: restore test closure and wildcard import that broke compilation

* refactor: split composite_to_surface to reduce cognitive complexity
2026-10-25 19:38:31 -04:00
vangel76 5d77de703e Merge remote-tracking branch 'origin/perf/9427-ahd-demosaic' into ahdfix-9455
# Conflicts:
#	crates/raw/src/demosaic.rs
2026-10-25 23:17:47 +01:00
vangel76andClaude Sonnet 5.5 3bceaf4d66 raw: AHD allocates its scratch once per worker, not once per band
The bands were split into four groups per worker, clamped to the number
of bands. A 24 MP raw has 126 bands, so with 32 workers every group held
one band and every band allocated and zeroed a fresh 16-plane scratch,
which cost more than the reuse saved. The bands are now split into one
group per worker, so the scratch is allocated once per worker whatever
the thread count. Pixels are unchanged.

Adds a test that runs 35 bands in groups of 9 on a 4-thread pool and
compares the result with the reference bit for bit, so the band index of
later groups and a scratch reused for several bands are checked.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 23:14:30 +01:00
David Mann 3370667b39 Integration: AHD scratch sized to the rows the memory check approved (#9450)
Each AHD worker allocated scratch for a full 38-row band while
develop's memory check budgets h.min(32) + 6 rows, so short images
allocated more than was approved: a 262144 x 1 strip was budgeted
~106 MB and allocated ~580 MB (5.4x). The scratch is now sized from
the same row count as the budget, and the budget also counts the two
u32 vertical-sum rows, so budget == allocation (~109 MB there).
Pixels are unchanged.

Tests: ahd_scratch_fits_its_budget (budget equals the real scratch for
h = 1..5000), ahd_matches_reference_with_reused_scratch (1100 rows,
jobs 1 and 2, all four phases: one scratch serves many bands).

Co-authored-by: vangel76 <271991477+vangel76@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 630e32c4238df452d9d5e70357bdb968045ab8c3)
2026-10-25 23:11:54 +01:00
David Mann 0dede9f952 Merge PR #9451: raw: faster lossless JPEG scan loop (inline diff, 2-component loop, shared tables) 2026-10-25 18:54:57 +01:00
David Mann 0134d2dc70 Revert "Merge PR #9455: raw: develop the default rendering band by band, without the f32 RGB frame"
This reverts commit ddb92be1fa, reversing
changes made to f12be92413.
2026-10-25 18:38:22 +01:00
David Mann 92f592684c Revert "Merge PR #9454: raw: develop_half, a fused half-size preview develop (part of #9433)"
This reverts commit f12be92413, reversing
changes made to ba503e3584.
2026-10-25 17:49:19 +01:00
6156d4b833 Integration: lossless JPEG round-trips with one Huffman table per component (#9451)
Every lossless-JPEG round-trip wrote a single DHT (id 0) shared by all
components, so the new per-component table mapping (comp_table, the t0/t1
split of the two-component loop) went untested, though real CR2 files
declare one table per component. testgen gains lj92_encode_tables (one DHT
per table id, Td chosen per component, code lengths rotated per table so
the tables differ); lj92_encode delegates to it with output unchanged.

New tests cover 2- and 4-component frames with distinct and mixed tables,
predictors 1, 4, 6 and 7, restart intervals and 16-bit 0/65535 extremes.
They now catch swapping t0/t1, pushing 0 into comp_table, and resolving
every component to tables[0]; they pass on main's decoder as well.

Co-authored-by: vangel76 <271991477+vangel76@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 17:22:28 +01:00
David Mann 34310d5ba6 Merge PR #8402: Raw: open lossy DNGs (floating-point JPEG XL raw data), and fall back to the JPEG XL preview, not the tiny thumbnail (fixes #8125) 2026-10-25 15:05:48 +01:00
David MannandClaude Opus 5.5 ddb92be1fa Merge PR #9455: raw: develop the default rendering band by band, without the f32 RGB frame
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 15:04:31 +01:00
David MannandClaude Opus 5.5 f12be92413 Merge PR #9454: raw: develop_half, a fused half-size preview develop (part of #9433)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 15:04:30 +01:00
David MannandClaude Opus 5.5 ba503e3584 Merge PR #9452: raw: block-wise final encode stage for the default rendering
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 15:04:29 +01:00
David MannandClaude Opus 5.5 be38fa9d11 Merge PR #9449: raw: grouped 10/12/14-bit unpack, single-strip buffer kept, in-place undelta
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 15:04:28 +01:00
David MannandClaude Opus 5.5 fd533fbaf9 Merge PR #9451: raw: faster lossless JPEG scan loop (inline diff, 2-component loop, shared tables)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 15:02:31 +01:00
dcf5c61a64 Integration: cap MapPolynomial opcodes, finite WhiteLevel, RGB-only JPEG XL preview (#8402)
- An opcode list may hold up to 4096 opcodes, and every MapPolynomial is checked for every
  sample, so a crafted DNG with thousands of whole-image polynomials (a few hundred KB) hung the
  open for minutes. Keep at most 64 per list; the rest are named as not applied.
- A DOUBLE WhiteLevel past f32::MAX became an infinite white, which blacked out that channel.
  A non-finite level now falls back to the default, as a zero one does.
- A reduced-resolution LinearRaw IFD (NewSubFileType 1, three samples, JPEG XL) was a JPEG XL
  preview candidate, and its undeveloped samples would show as an sRGB picture. Previews now
  need PhotometricInterpretation RGB or YCbCr.
- README: floating-point JPEG XL raw data and OpcodeList2 MapPolynomial are supported.

Co-authored-by: SBaksa <18648812+SBaksa@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 14:55:45 +01:00
vangel76andClaude Sonnet 5.5 9e2426f6c1 Format the AHD bit-for-bit test with rustfmt
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 14:52:26 +01:00
vangel76andClaude Sonnet 5.5 d90ae8386a raw: faster tone stage (Shadows, Highlights, Clarity, Texture, Dehaze), same bytes
The full-size part of the tone stage cost as much as the whole AHD
development at 45 MP. Everything changed here keeps the arithmetic and
its order, so the developed output is bit for bit the same:

- The bilinear lookup of the reduced change maps (and of Dehaze's
  transmission) worked out the source position with divisions for every
  pixel and did it twice, once for each of the two planes. The taps of
  every column and row are now computed once per image, and both planes
  of a change are stored together.
- Texture's Gaussian bases ran their column pass down strided columns,
  one tap at a time with a mirror per tap. Both passes now accumulate
  neighbouring samples side by side, in the same tap order.
- The box mean's column pass runs row-wise the same way, blocks in
  parallel.
- The luminance reduced for the work copy and for Texture's copy is
  worked out once per pixel instead of twice, and the work copy is not
  reduced at all when only Texture is set and there is no black point.
- log2 of the luminance is taken once for both changes.

Unit tests keep the replaced code and compare the bits.

Part of #9435.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 14:49:45 +01:00
vangel76andClaude Sonnet 5.5 dc791a43de raw: develop the default rendering band by band without the f32 RGB frame
When neither Dehaze, a local tone slider nor a camera black point is in play, no
statistic of the whole frame is needed, so the demosaic output no longer has to be
kept. Every band is demosaiced into a small band buffer and encoded straight into the
u16 output. The f32 RGB frame (12 bytes a pixel, 545 MB at 45 MP) and the pass that
read it back are gone. A Bayer sensor also skips the padded CFA plane: each band
normalizes the rows it reads (with the mirrored borders) into a small window. X-Trans
keeps its plane and green plane, LinearRaw has none. Tone, Dehaze and black points
keep the full-frame path.

The bands are the ones the full-frame path computes, with the same arithmetic per
pixel, so the output is bit-identical (tests compare both paths for Bayer, X-Trans and
LinearRaw, odd sizes, band borders, orientations and 1, 3 and 16 threads).
working_memory now charges what each path really holds.

Fixes #9434

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 14:35:29 +01:00
vangel76andClaude Sonnet 5.5 132af3807b raw: add develop_half, a fused half-size preview develop
develop_half bins every 2x2 block of a Bayer mosaic (3x3 for X-Trans,
2x2 box for LinearRaw and monochrome) and fuses black/white scaling,
white balance with its clip, gain maps, the colour matrix and the gamma
encode into one pass, with no demosaic and no full-size float frame.
The result is a Developed in the same colour space, type and
orientation as develop_sensor's, at half size, so a viewer can show it
first and swap the full develop in later. Tone sliders and the camera
black point are not applied (a warning says so). Part of #9433.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 14:32:10 +01:00
vangel76 c7c5aae4af Merge remote-tracking branch 'origin/perf/9428-raw-encode-stage' into perf/9434-fused-develop 2026-10-25 14:14:18 +01:00
vangel76 0966e14ae0 Merge origin/perf/9427-ahd-demosaic (keep clampf from the R1 fix) 2026-10-25 14:14:18 +01:00
vangel76andClaude Sonnet 5.5 df23873ccf raw: block-wise final encode stage for the default rendering
When neither Dehaze nor a local tone map is active (neutral tone and no
black point; only the Nikon D4 profile sets one), the matrix is applied
into a block of 768 samples and the gamma encode runs over the block:
clamp and square root first (vectorises), then the table with
[value, step] pairs. Same arithmetic, same output; other renderings keep
the old per-sample loop. A test compares the block encode with the
per-sample encode.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 14:05:24 +01:00
vangel76andClaude Sonnet 5.5 cc7da71b47 raw: faster lossless JPEG scan loop (inline diff, 2-component loop, shared tables)
The predictor 1 row loop called Bits::diff out of line, indexed the
per-component tables with bounds checks, and kept a private copy of
the 48 KiB Huffman lookup pair per component even when the components
share one DHT (CR2 and DNG two-component frames).

- Bits::diff is always inlined.
- Rows with one or two components carry Ra in registers and walk the
  row with chunks of two.
- Components that name the same table share one copy.

The frame size cap, the one-bit-per-sample truncation guard and the
DHT rebuild cap are untouched.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 14:02:03 +01:00
vangel76andClaude Sonnet 5.5 d9aed18582 raw: AHD demosaic with reused planar scratch, about 1.6x fewer instructions
Same arithmetic in the same order, so the output is bit for bit the same
(the old code stays under cfg(test) as ahd_ref and a test compares them).
Scratch planes are allocated once per group of bands instead of per band
(no per-band zeroing or page faults) and never cleared, since every cell
a stage reads was written earlier in the same band. The layout is planar
RGB/Lab, the candidate pixel is built from values instead of indexed
stores (which stalled store forwarding), the 3x3 homogeneity sum is
separable, and the LabF table holds [value, step] pairs. The 32-row band
is kept, as a larger band costs peak memory.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 14:01:56 +01:00
vangel76andClaude Sonnet 5.5 e15d68bfbb raw: unpack 10/12/14-bit rows by groups, keep a single strip's buffer
Bit-packed uncompressed raw data was read one sample at a time through
a bit accumulator. Rows of 10, 12 and 14-bit samples are now read a
whole group (4x10, 2x12, 4x14 bits = 5, 3, 7 bytes) at a time; the
row tail still goes through the general reader. A file whose only
segment is the whole image row for row no longer copies it into a
second full-size buffer: the decoded Vec becomes the plane. The
predictor 2 undo works in place instead of on a copy.

The summed-segment charge (#4959) and the per-segment limits are
unchanged. A test compares the grouped reader with the plain bit
reader for 10/12/14 bits, 1 and 3 samples and several widths.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 14:00:24 +01:00
vangel76andClaude Sonnet 5.5 97a719a7a7 raw: a non-finite black level no longer panics the AHD demosaic
A DNG whose BlackLevel is a DOUBLE beyond f32 reads as inf. The periodic
fill then computed (raw - inf) * 0 = NaN, and AHD's f32::clamp with NaN
bounds asserts. The fill now returns (0, 0) for a non-finite black, as
Scale::value does, and AHD uses a compare-and-select clamp that gives the
same result for every non-NaN bound.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 13:58:12 +01:00
aiongg 7aaa59712a Merge main into integration-batch-7 2026-10-23 20:37:05 +00:00
Dylan M. Taylor 78ae50f7b2 security: bisect flat-table and gradient-stop lookups; charge ICC curve samples; bound PSD planes, RAW ljpeg retention and TIFF dump preview 2026-10-23 14:41:53 -04:00
SBaksa 319cb299d2 Merge remote-tracking branch 'origin/main' into dng-lossy-open 2026-10-23 10:49:33 +11:00
SBaksa 65ac51e421 Raw: apply OpcodeList2 MapPolynomial, the per-plane tone mapping lossy DNGs depend on (#8125)
A DNG converter's lossy output carries one MapPolynomial opcode per
plane in OpcodeList2, and its colours and contrast depend on it. The
opcode was reported as not applied, so such a file now opened at full
size from its raw data but with the wrong colours.

opcodes::parse now returns Opcodes { gain_maps, polynomials, skipped }
and reads MapPolynomial (ID 8: area, planes, pitches, degree up to 8,
coefficients). The DNG reader applies OpcodeList2's polynomials to the
samples as it decodes them, on the linear reference values (after the
black and white levels, input and output clipped to 0..1), so
development, white balance and the camera-look fit all see the mapped
data. A LinearizationTable is applied first and is then done with; for
floating-point data, which is linear already, it is ignored.
2026-10-23 10:47:17 +11:00
SBaksa a4fc3a714e Raw: scale floating-point samples by the highest white level, not the first
WhiteLevel holds one value per sample plane, but the float scale came
from the first only. A plane whose white level was higher than the
first's was clipped before the DNG reader applied its own level. The
scale now comes from the highest valid white level (default 1.0).
2026-10-23 09:58:18 +11:00
David Mann b1e4c9b1f9 Merge PR #6041: Raw: charge the retained tile intermediates together (#4959) 2026-10-22 23:40:15 +02:00
f40366f1a8 Integration: keep only the segment of a lossless-JPEG tile, test the sum through decode (#6041)
A compression-7 frame may declare up to four times its segment's samples
(ljpeg::decode is capped at 4x the tile), and read_plane returned the whole
frame, so each retained tile held up to 4x what retained_segment_bytes
charges it: tiles declared 256x256 holding 512x512 frames pass a 1 MiB
budget while keeping 2 MiB. The lossless-JPEG arm moves into
lossless_jpeg(), which now truncates to the segment's rows (all the scatter
reads) and releases the spare capacity.

The PR's test only called the helper; deleting the call in read_plane kept
it green. decode_charges_the_retained_tiles_together drives decode on an
18x18 CFA in four padded 16x16 LJ92 tiles: 1500 B -> LimitExceeded, 2048 B
-> Ok. lossless_jpeg_keeps_only_the_segment pins the truncation. The doc
comment now says the output plane is alive alongside the sum.

Co-authored-by: Muhammad Saad <188063027+saad-mughal435@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 21:09:26 +02:00
aiongg ffa400c754 Keep tests correct and docs current with one test binary per crate
- gpu: the test modules share one GPU_LOCK, and timing and vector_mask
  take it too, so one process drives one wgpu device at a time.
- ui-egui: the wgpu tests in pattern_preview_canvas, adjust_preview_gpu,
  marquee_autoscroll and layout_perf take gpu_lock().
- affinity compound_limits: the child process selects its test by the
  module path (compound_limits::<name>); with --exact, the bare name
  matches no test and the child passes without running the check.
- xtask scorecard reads crates/io/tests/it/corpus.rs.
- Docs and comments name the new test paths and the --test it filters.
2026-10-22 16:55:34 +00:00
aiongg a6428d3dfb Build each crate's integration tests as one binary
Every tests/*.rs file is its own executable that links the whole crate
graph. Move each crate's test files into tests/it/ as modules of one
tests/it/main.rs; helper modules under tests/common move with them.

These keep their own binary (own process):
- engine panic_hunt: a process-wide panic hook counts every panic.
- engine text_gamma: changes the process-wide text gamma.
- engine smart_filter_stages, mask_cache_purge: assert on global caches.
- codecs pnm_peak_memory: reads the process's peak RSS.
- photocraft mac_menu_appkit: harness = false.
2026-10-22 16:55:34 +00:00
aiongg 934eb71d84 Integration: merge main
crates/engine/src/file_cmds.rs: batch keeps the save-point closure (takes the output path) and also copies the session's authorize hook into each scratch session, as main now does.
crates/raw/src/sensor.rs: both sides cap the memory of retained tiles; kept the existing retained_segment_bytes check (checked multiplication, same limit) and dropped main's duplicate inline loop.
2026-10-22 15:39:51 +00:00
aiongg f72c3c2187 Integration: track t08
# Conflicts:
#	crates/ui-egui/src/camera_raw_ui.rs
#	crates/ui-egui/src/jobs_ui.rs
#	crates/ui-egui/src/widgets.rs
#	docs/file-format-parity.md
#	docs/gaps.md
2026-10-22 15:38:23 +00:00
aiongg 0e80156397 Integration: track t16
Resolutions: CLI usage, flag lists and flag parsing carry both --tiff-compression (t15) and
--jpeg-format (t16); file.saveACopy, batch, imageProcessor and layersToFiles param specs carry
both tiffCompression and jpegFormat; file_cmds keeps tiff_compression_param and
jpeg_format_param; atn_cmds keeps flag_set/applies_at_canvas_bounds (t15) and UNHOLDABLE (t16),
edit.transform uses the pixel_target guard (t15) with layer_ref(p)? (t16); ui-egui open paths
build ImportOptions (t16) and apply raw_defaults (t15); docs headers/rows unioned; atn tests keep
both sides; tr.tsv "Keep Using CPU" kept once.
2026-10-22 15:38:13 +00:00