mirror of
https://github.com/UglyToad/PdfPig.git
synced 2026-03-10 00:23:29 +08:00
Compare commits
6 Commits
support-co
...
icc-color-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af7aefb096 | ||
|
|
2c5cb69a64 | ||
|
|
57ed17e7c4 | ||
|
|
1517b8fe39 | ||
|
|
254321e906 | ||
|
|
be9a9d7892 |
@@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net45;net451;net452;net46;net461;net462;net47;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard2.0;net452;net46;net461;net462;net47;net6.0</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Version>0.1.8-alpha-001</Version>
|
||||
<IsTestProject>False</IsTestProject>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net45;net451;net452;net46;net461;net462;net47;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard2.0;net452;net46;net461;net462;net47;net6.0</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Version>0.1.8-alpha-001</Version>
|
||||
<IsTestProject>False</IsTestProject>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net45;net451;net452;net46;net461;net462;net47;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard2.0;net452;net46;net461;net462;net47;net6.0</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Version>0.1.8-alpha-001</Version>
|
||||
<IsTestProject>False</IsTestProject>
|
||||
|
||||
@@ -172,7 +172,8 @@
|
||||
numberOfColorComponents: 3,
|
||||
alternateColorSpaceDetails: DeviceRgbColorSpaceDetails.Instance,
|
||||
range: new List<decimal> { 0, 1, 0, 1, 0, 1 },
|
||||
metadata: null),
|
||||
metadata: null,
|
||||
rawProfile: null),
|
||||
DecodedBytes = decodedBytes,
|
||||
WidthInSamples = 1,
|
||||
HeightInSamples = 1,
|
||||
@@ -193,7 +194,8 @@
|
||||
numberOfColorComponents: 3,
|
||||
alternateColorSpaceDetails: DeviceRgbColorSpaceDetails.Instance,
|
||||
range: new List<decimal> { 0, 1, 0, 1, 0, 1 },
|
||||
metadata: null),
|
||||
metadata: null,
|
||||
rawProfile: null),
|
||||
DecodedBytes = decodedBytes,
|
||||
WidthInSamples = 1,
|
||||
HeightInSamples = 1,
|
||||
@@ -212,7 +214,6 @@
|
||||
Assert.True(PngFromPdfImageFactory.TryGenerate(iccBasedImage, out var iccPngBytes));
|
||||
Assert.True(PngFromPdfImageFactory.TryGenerate(deviceRGBImage, out var deviceRgbBytes));
|
||||
Assert.Equal(iccPngBytes, deviceRgbBytes);
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -17,6 +17,127 @@
|
||||
Directory.CreateDirectory(OutputFolder);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void IccXyzTest()
|
||||
{
|
||||
var path = IntegrationHelpers.GetSpecificTestDocumentPath("xyztest.pdf");
|
||||
using (var document = PdfDocument.Open(path))
|
||||
{
|
||||
var page1 = document.GetPage(1);
|
||||
var paths = page1.ExperimentalAccess.Paths
|
||||
.OrderBy(p => p.GetBoundingRectangle().Value.Centroid.X)
|
||||
.ThenBy(p => p.GetBoundingRectangle().Value.Centroid.Y)
|
||||
.ToArray();
|
||||
|
||||
var grouped = page1.ExperimentalAccess.Paths
|
||||
.GroupBy(p => p.GetBoundingRectangle().Value.Centroid.X)
|
||||
.ToArray();
|
||||
|
||||
int i = 0; // Red
|
||||
var gr = grouped[i].ToArray();
|
||||
Assert.Equal(3, gr.Length);
|
||||
Assert.True(gr[1].IsFilled);
|
||||
var actualColor = gr[1].FillColor.ToRGBValues();
|
||||
byte rAct = ConvertToByte(actualColor.r);
|
||||
byte gAct = ConvertToByte(actualColor.g);
|
||||
byte bAct = ConvertToByte(actualColor.b);
|
||||
Assert.Equal(255, rAct);
|
||||
Assert.Equal(0, gAct);
|
||||
Assert.Equal(0, bAct);
|
||||
|
||||
// pdf.js - not correct
|
||||
var firstRow = gr[0].FillColor.ToRGBValues();
|
||||
Assert.Equal(111, ConvertToByte(firstRow.r));
|
||||
Assert.Equal(57, ConvertToByte(firstRow.g));
|
||||
Assert.Equal(4, ConvertToByte(firstRow.b));
|
||||
|
||||
// pdf.js - not correct
|
||||
var thirdRow = gr[2].FillColor.ToRGBValues();
|
||||
Assert.Equal(115, ConvertToByte(thirdRow.r));
|
||||
Assert.Equal(57, ConvertToByte(thirdRow.g));
|
||||
Assert.Equal(4, ConvertToByte(thirdRow.b));
|
||||
|
||||
i = 1; // Green
|
||||
|
||||
i = 2; // Blue
|
||||
|
||||
i = 3; // White
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IccLabProfile16bitLUTv2()
|
||||
{
|
||||
var path = IntegrationHelpers.GetSpecificTestDocumentPath("icc-lab2.pdf");
|
||||
using (var document = PdfDocument.Open(path))
|
||||
{
|
||||
// page 1
|
||||
var page1 = document.GetPage(1);
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IccLabProfile8bitLUT()
|
||||
{
|
||||
var path = IntegrationHelpers.GetSpecificTestDocumentPath("icc-lab-8bit.pdf");
|
||||
using (var document = PdfDocument.Open(path))
|
||||
{
|
||||
var page1 = document.GetPage(1);
|
||||
var paths = page1.ExperimentalAccess.Paths
|
||||
.OrderBy(p => p.GetBoundingRectangle().Value.Centroid.X)
|
||||
.ThenBy(p => p.GetBoundingRectangle().Value.Centroid.Y)
|
||||
.ToArray();
|
||||
|
||||
var grouped = page1.ExperimentalAccess.Paths
|
||||
.GroupBy(p => p.GetBoundingRectangle().Value.Centroid.X)
|
||||
.ToDictionary(kvp => kvp.Key, kvp => kvp);
|
||||
|
||||
/*
|
||||
* If all the profiles were rendered correctly, for the 3 "Lab" files we'd expect to see:
|
||||
* stripe 1: lab(0 0 0) - black
|
||||
* stripe 2: lab(100% 0 0) - white
|
||||
* stripe 3: lab(75% 0 50) - mustard (#CEB759)
|
||||
* stripe 4: lab(75% 50 0) - pink (#FF92BB)
|
||||
*/
|
||||
|
||||
var black = grouped[30].Single().FillColor;
|
||||
var white = grouped[60].Single().FillColor;
|
||||
var mustard = grouped[90].Single().FillColor; // rgb(206,183,89)
|
||||
var pink = grouped[120].Single().FillColor; // rgb(255,146,187)
|
||||
|
||||
AssertColor(206, 183, 89, mustard);
|
||||
AssertColor(255, 146, 187, pink);
|
||||
}
|
||||
}
|
||||
|
||||
private void AssertColor(byte r, byte g, byte b, IColor color)
|
||||
{
|
||||
var rgb = color.ToRGBValues();
|
||||
var rAct = ConvertToByte(rgb.r);
|
||||
var gAct = ConvertToByte(rgb.g);
|
||||
var bAct = ConvertToByte(rgb.b);
|
||||
|
||||
Assert.Equal(r, rAct);
|
||||
Assert.Equal(g, gAct);
|
||||
Assert.Equal(b, bAct);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IccLabProfileMatrixv4()
|
||||
{
|
||||
var path = IntegrationHelpers.GetSpecificTestDocumentPath("icc-lab4.pdf");
|
||||
using (var document = PdfDocument.Open(path))
|
||||
{
|
||||
// page 1
|
||||
var page1 = document.GetPage(1);
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IndexedDeviceNColorSpaceImages()
|
||||
{
|
||||
@@ -259,11 +380,12 @@
|
||||
for (int p = 0; p < document.NumberOfPages; p++)
|
||||
{
|
||||
var page = document.GetPage(p + 1);
|
||||
foreach (var image in page.GetImages())
|
||||
var images = page.GetImages().ToArray();
|
||||
for (int i = 0; i < images.Length; i++)
|
||||
{
|
||||
if (image.TryGetPng(out var png))
|
||||
if (images[i].TryGetPng(out var png))
|
||||
{
|
||||
// TODO
|
||||
File.WriteAllBytes(Path.Combine(OutputFolder, $"Pig Production Handbook_{p + 1}_{i}.png"), png);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -279,7 +401,12 @@
|
||||
{
|
||||
Page page1 = document.GetPage(1);
|
||||
var paths1 = page1.ExperimentalAccess.Paths.Where(p => p.IsFilled).ToArray();
|
||||
Assert.Equal((0.930496m, 0.111542m, 0.142197m), paths1[0].FillColor.ToRGBValues()); // 'Reflex Red' Separation color space
|
||||
|
||||
// 'Reflex Red' Separation color space
|
||||
var reflexRed = paths1[0].FillColor.ToRGBValues();
|
||||
Assert.Equal(237, ConvertToByte(reflexRed.r));
|
||||
Assert.Equal(28, ConvertToByte(reflexRed.g));
|
||||
Assert.Equal(36, ConvertToByte(reflexRed.b));
|
||||
|
||||
Page page2 = document.GetPage(2);
|
||||
var words = page2.GetWords(NearestNeighbourWordExtractor.Instance).ToArray();
|
||||
@@ -287,16 +414,19 @@
|
||||
var urlWord = words.First(w => w.ToString().Contains("www.extron.com"));
|
||||
var firstLetter = urlWord.Letters[0];
|
||||
Assert.Equal("w", firstLetter.Value);
|
||||
Assert.Equal((0, 0, 1), firstLetter.Color.ToRGBValues()); // Blue
|
||||
var blue = firstLetter.Color.ToRGBValues();
|
||||
Assert.Equal(0, ConvertToByte(blue.r));
|
||||
Assert.Equal(0, ConvertToByte(blue.g));
|
||||
Assert.Equal(255, ConvertToByte(blue.b));
|
||||
|
||||
var paths2 = page2.ExperimentalAccess.Paths;
|
||||
var filledPath = paths2.Where(p => p.IsFilled).ToArray();
|
||||
var filledRects = filledPath.Where(p => p.Count == 1 && p[0].IsDrawnAsRectangle).ToArray();
|
||||
|
||||
// Colors picked from Acrobat reader
|
||||
(decimal r, decimal g, decimal b) lightRed = (0.985m, 0.942m, 0.921m);
|
||||
(decimal r, decimal g, decimal b) lightRed2 = (1m, 0.95m, 0.95m);
|
||||
(decimal r, decimal g, decimal b) lightOrange = (0.993m, 0.964m, 0.929m);
|
||||
(decimal r, decimal g, decimal b) lightRed = (251, 240, 235);
|
||||
(decimal r, decimal g, decimal b) lightRed2 = (255, 242, 242);
|
||||
(decimal r, decimal g, decimal b) lightOrange = (253, 246, 237);
|
||||
|
||||
var filledColors = filledRects
|
||||
.OrderBy(x => x.GetBoundingRectangle().Value.Left)
|
||||
@@ -312,16 +442,25 @@
|
||||
{
|
||||
if (r == 2)
|
||||
{
|
||||
Assert.Equal(lightRed2, color.ToRGBValues());
|
||||
var red = color.ToRGBValues();
|
||||
Assert.Equal(lightRed2.r, ConvertToByte(red.r));
|
||||
Assert.Equal(lightRed2.g, ConvertToByte(red.g));
|
||||
Assert.Equal(lightRed2.b, ConvertToByte(red.b));
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Equal(lightRed, color.ToRGBValues());
|
||||
var red = color.ToRGBValues();
|
||||
Assert.Equal(lightRed.r, ConvertToByte(red.r));
|
||||
Assert.Equal(lightRed.g, ConvertToByte(red.g));
|
||||
Assert.Equal(lightRed.b, ConvertToByte(red.b));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Equal(lightOrange, color.ToRGBValues());
|
||||
var orange = color.ToRGBValues();
|
||||
Assert.Equal(lightOrange.r, ConvertToByte(orange.r));
|
||||
Assert.Equal(lightOrange.g, ConvertToByte(orange.g));
|
||||
Assert.Equal(lightOrange.b, ConvertToByte(orange.b));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -329,7 +468,7 @@
|
||||
|
||||
private static byte ConvertToByte(decimal componentValue)
|
||||
{
|
||||
var rounded = Math.Round(componentValue * 255, MidpointRounding.AwayFromZero);
|
||||
var rounded = Math.Round(componentValue * 255m, MidpointRounding.AwayFromZero);
|
||||
return (byte)rounded;
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,94 @@
|
||||
%PDF-1.3
|
||||
%ßþø²
|
||||
1 0 obj
|
||||
<</Type/Catalog/Pages 3 0 R/Lang()/Metadata 4 0 R>>
|
||||
endobj
|
||||
2 0 obj
|
||||
<</Producer(http://bfo.com/products/publisher?version=alpha-work-39081:39082M-20210205T103556)/CreationDate(D:20210205103611Z)/Subject(ICC test: Lab v2 \(LUT\))/ModDate(D:20210205103611Z)>>
|
||||
endobj
|
||||
3 0 obj
|
||||
<</Type/Pages/Kids[5 0 R]/Count 1>>
|
||||
endobj
|
||||
4 0 obj
|
||||
<</Type/Metadata/Subtype/XML/Length 1742>>stream
|
||||
<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?>
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description xmlns:pdf="http://ns.adobe.com/pdf/1.3/" rdf:about=""><pdf:Producer>http://bfo.com/products/publisher?version=alpha-work-39081:39082M-20210205T103556</pdf:Producer></rdf:Description><rdf:Description xmlns:dc="http://purl.org/dc/elements/1.1/" rdf:about=""><dc:description><rdf:Alt><rdf:li xml:lang="x-default">ICC test: Lab v2 (LUT)</rdf:li></rdf:Alt></dc:description><dc:format>application/pdf</dc:format></rdf:Description><rdf:Description xmlns:xmp="http://ns.adobe.com/xap/1.0/" rdf:about=""><xmp:CreateDate>2021-02-05T10:36:11Z</xmp:CreateDate><xmp:MetadataDate>2021-02-05T10:36:11Z</xmp:MetadataDate><xmp:ModifyDate>2021-02-05T10:36:11Z</xmp:ModifyDate></rdf:Description><rdf:Description xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#" xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" rdf:about=""><xmpMM:DerivedFrom><rdf:Description><stRef:lastModifyDate>2021-02-05T10:12:42Z</stRef:lastModifyDate><stRef:documentID>file:/Users/mike/bfo/trunk/bforeport2/testcases/matts/mike/icc/icc-lab2.svg</stRef:documentID></rdf:Description></xmpMM:DerivedFrom><xmpMM:DocumentID>uuid:6759e64c-f196-3fa0-ef90-7a6df808637d</xmpMM:DocumentID><xmpMM:History><rdf:Seq><rdf:li><rdf:Description><stEvt:softwareAgent>BFO Publisher work-39081:39082M-20210205T103556</stEvt:softwareAgent><stEvt:action>converted</stEvt:action><stEvt:when>2021-02-05T10:36:11Z</stEvt:when></rdf:Description></rdf:li></rdf:Seq></xmpMM:History><xmpMM:InstanceID>uuid:6759e64c-f196-3fa0-ef90-7a6df808637d</xmpMM:InstanceID></rdf:Description></rdf:RDF>
|
||||
<?xpacket end='r'?>
|
||||
endstream
|
||||
endobj
|
||||
5 0 obj
|
||||
<</Type/Page/MediaBox[0 0 150 150]/Resources<</ProcSet[/PDF/Text]/Font<</R1 6 0 R>>/ColorSpace<</R2 8 0 R>>>>/Contents[7 0 R]/Parent 3 0 R>>
|
||||
endobj
|
||||
6 0 obj
|
||||
<</Type/Font/BaseFont/Helvetica/Subtype/Type1/FontDescriptor 9 0 R/FirstChar 32/LastChar 251/Widths[278 278 355 556 556 889 667 222 333 333 389 584 278 333 278 278 556 556 556 556 556 556 556 556 556 556 278 278 584 584 584 556 1015 667 667 722 722 667 611 778 722 278 500 667 556 833 722 778 667 778 722 667 611 722 667 944 667 667 611 278 278 278 469 556 222 556 556 500 556 556 278 556 556 222 222 500 222 833 556 556 556 556 333 500 278 556 500 722 500 500 500 334 260 334 584 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 333 556 556 167 556 556 556 556 191 333 556 333 333 500 500 0 556 556 556 278 0 537 350 222 333 333 556 1000 1000 0 611 0 333 333 333 333 333 333 333 333 0 333 333 0 333 333 333 1000 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1000 0 370 0 0 0 0 556 778 1000 365 0 0 0 0 0 889 0 0 0 278 0 0 222 611 944 611]>>
|
||||
endobj
|
||||
7 0 obj
|
||||
<</Length 810>>stream
|
||||
0.75 0 0 0.75 0 0 cm
|
||||
q
|
||||
q
|
||||
Q
|
||||
BT
|
||||
0 0 0 1 k
|
||||
30.097 185 Td
|
||||
/R1 9 Tf [(ICC pro®le of type=Lab \(16bit LUT\))3344]TJ
|
||||
-13.785 -15 Td
|
||||
/R1 4 Tf [(https://github)40(.com/ellelstone/elles_icc_pro®les/b)20(lob/master/pro®les/Lab-D50-Identity-elle-V2.icc)4078]TJ
|
||||
ET
|
||||
20 159.5 m 180 159.5 l 180 160 l 179.5 160 l 179.5 30 l 180 30 l 180 30.5 l 20 30.5 l 20 30 l 20.5 30 l 20.5 160 l 20 160 l h 19.5 160 m 19.5 30 l 19.5 29.5 l 20 29.5 l 180 29.5 l 180.5 29.5 l 180.5 30 l 180.5 160 l 180.5 160.5 l 180 160.5 l 20 160.5 l 19.5 160.5 l h f
|
||||
/R2 cs
|
||||
0 0 0 scn
|
||||
20 30 40 130 re f
|
||||
1 0 0 sc
|
||||
60 30 40 130 re f
|
||||
0.75 0 0.5 sc
|
||||
100 30 40 130 re f
|
||||
0.75 0.5 0 sc
|
||||
140 30 40 130 re f
|
||||
BT
|
||||
0 0 0 1 k
|
||||
36.608 15 Td
|
||||
[(L=0)9152]TJ
|
||||
40 0 Td
|
||||
[(L=1)19152]TJ
|
||||
24.816 0 Td
|
||||
[(L=0.75 "a"=0 "b"=0.5)25356]TJ
|
||||
39.29 0 Td
|
||||
[(L=0.75 "a"=0.5" "b"=0)35178]TJ
|
||||
ET
|
||||
Q
|
||||
q
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
8 0 obj
|
||||
[/ICCBased 10 0 R]
|
||||
endobj
|
||||
9 0 obj
|
||||
<</Type/FontDescriptor/FontName/Helvetica/Flags 32/FontBBox[-166 -225 1000 931]/ItalicAngle 0/Ascent 718/Descent -207/CapHeight 718/XHeight 523/StemV 88>>
|
||||
endobj
|
||||
10 0 obj
|
||||
<</Filter/FlateDecode/Length 345/N 3>>stream
|
||||
xœQÁJÃ@<10>¤O‚ö¶G+MR[ð ^ÚâÍ[¬·d3M6›<36><E280BA>Úü¿À<C2BF>ðè?žý
|
||||
OB<EFBFBD>´–€´àÁ†yoxo3˸/Z¦Ö=#K7a$ª:ø€&8p-pBió3£–°Ÿïìe¼yÕYû};ÑŒÑJî¯\Ï2/À‰™?-(¯øóc9yVm <09>AoØe~Åüñ'[á¤Ú[ÅhHQ)¢¹Òä)Sÿ§7BÄ€`€˜”<‰`Î\³ò¸øW.©ê£,/•ÌHôºçq5Š1eÅéŒ(¿£Æ˜ˆ6B<36>-|™¥A»#F#o8ñÆÑ÷»âÖäYAoR–c²À<C2B2>Ô²;ÍŒõ³" ´’h,Ú *=4&¡–YŒm¿Úçnr/6Öïg§ýÞzäüºÄ<0E>Niím4Ü?ùÁåV«º¶¨õj<C3B5>z¶QußÖ7Óˈ”
|
||||
endstream
|
||||
endobj
|
||||
xref
|
||||
0 11
|
||||
0000000000 65535 f
|
||||
0000000015 00000 n
|
||||
0000000082 00000 n
|
||||
0000000287 00000 n
|
||||
0000000338 00000 n
|
||||
0000002156 00000 n
|
||||
0000002312 00000 n
|
||||
0000003174 00000 n
|
||||
0000004033 00000 n
|
||||
0000004067 00000 n
|
||||
0000004237 00000 n
|
||||
trailer
|
||||
<</Root 1 0 R/Info 2 0 R/ID[<6759e64cf1963fa0ef907a6df808637d><6759e64cf1963fa0ef907a6df808637d>]/Size 11>>
|
||||
startxref
|
||||
4655
|
||||
%%EOF
|
||||
@@ -0,0 +1,94 @@
|
||||
%PDF-1.3
|
||||
%ßþø²
|
||||
1 0 obj
|
||||
<</Type/Catalog/Pages 3 0 R/Lang()/Metadata 4 0 R>>
|
||||
endobj
|
||||
2 0 obj
|
||||
<</Producer(http://bfo.com/products/publisher?version=alpha-work-39081:39082M-20210205T103525)/CreationDate(D:20210205103539Z)/Subject(ICC test: Lab v4)/ModDate(D:20210205103539Z)>>
|
||||
endobj
|
||||
3 0 obj
|
||||
<</Type/Pages/Kids[5 0 R]/Count 1>>
|
||||
endobj
|
||||
4 0 obj
|
||||
<</Type/Metadata/Subtype/XML/Length 1736>>stream
|
||||
<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?>
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description xmlns:pdf="http://ns.adobe.com/pdf/1.3/" rdf:about=""><pdf:Producer>http://bfo.com/products/publisher?version=alpha-work-39081:39082M-20210205T103525</pdf:Producer></rdf:Description><rdf:Description xmlns:dc="http://purl.org/dc/elements/1.1/" rdf:about=""><dc:description><rdf:Alt><rdf:li xml:lang="x-default">ICC test: Lab v4</rdf:li></rdf:Alt></dc:description><dc:format>application/pdf</dc:format></rdf:Description><rdf:Description xmlns:xmp="http://ns.adobe.com/xap/1.0/" rdf:about=""><xmp:CreateDate>2021-02-05T10:35:39Z</xmp:CreateDate><xmp:MetadataDate>2021-02-05T10:35:39Z</xmp:MetadataDate><xmp:ModifyDate>2021-02-05T10:35:39Z</xmp:ModifyDate></rdf:Description><rdf:Description xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#" xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" rdf:about=""><xmpMM:DerivedFrom><rdf:Description><stRef:lastModifyDate>2021-02-05T10:15:04Z</stRef:lastModifyDate><stRef:documentID>file:/Users/mike/bfo/trunk/bforeport2/testcases/matts/mike/icc/icc-lab4.svg</stRef:documentID></rdf:Description></xmpMM:DerivedFrom><xmpMM:DocumentID>uuid:3dfb3504-c34e-de9f-ecfe-c1e3650b77b3</xmpMM:DocumentID><xmpMM:History><rdf:Seq><rdf:li><rdf:Description><stEvt:softwareAgent>BFO Publisher work-39081:39082M-20210205T103525</stEvt:softwareAgent><stEvt:action>converted</stEvt:action><stEvt:when>2021-02-05T10:35:39Z</stEvt:when></rdf:Description></rdf:li></rdf:Seq></xmpMM:History><xmpMM:InstanceID>uuid:3dfb3504-c34e-de9f-ecfe-c1e3650b77b3</xmpMM:InstanceID></rdf:Description></rdf:RDF>
|
||||
<?xpacket end='r'?>
|
||||
endstream
|
||||
endobj
|
||||
5 0 obj
|
||||
<</Type/Page/MediaBox[0 0 150 150]/Resources<</ProcSet[/PDF/Text]/Font<</R1 7 0 R>>/ColorSpace<</R2 8 0 R>>>>/Contents[6 0 R]/Parent 3 0 R>>
|
||||
endobj
|
||||
6 0 obj
|
||||
<</Length 812>>stream
|
||||
0.75 0 0 0.75 0 0 cm
|
||||
q
|
||||
q
|
||||
Q
|
||||
BT
|
||||
0 0 0 1 k
|
||||
37.288 185 Td
|
||||
/R1 9 Tf [(ICC pro®le of type=Lab \(Matr)-14(ix\))4143]TJ
|
||||
-20.976 -15 Td
|
||||
/R1 4 Tf [(https://github)40(.com/ellelstone/elles_icc_pro®les/b)20(lob/master/pro®les/Lab-D50-Identity-elle-V4.icc)4078]TJ
|
||||
ET
|
||||
20 159.5 m 180 159.5 l 180 160 l 179.5 160 l 179.5 30 l 180 30 l 180 30.5 l 20 30.5 l 20 30 l 20.5 30 l 20.5 160 l 20 160 l h 19.5 160 m 19.5 30 l 19.5 29.5 l 20 29.5 l 180 29.5 l 180.5 29.5 l 180.5 30 l 180.5 160 l 180.5 160.5 l 180 160.5 l 20 160.5 l 19.5 160.5 l h f
|
||||
/R2 cs
|
||||
0 0 0 scn
|
||||
20 30 40 130 re f
|
||||
1 0 0 sc
|
||||
60 30 40 130 re f
|
||||
0.75 0 0.5 sc
|
||||
100 30 40 130 re f
|
||||
0.75 0.5 0 sc
|
||||
140 30 40 130 re f
|
||||
BT
|
||||
0 0 0 1 k
|
||||
36.608 15 Td
|
||||
[(L=0)9152]TJ
|
||||
40 0 Td
|
||||
[(L=1)19152]TJ
|
||||
24.816 0 Td
|
||||
[(L=0.75 "a"=0 "b"=0.5)25356]TJ
|
||||
39.29 0 Td
|
||||
[(L=0.75 "a"=0.5" "b"=0)35178]TJ
|
||||
ET
|
||||
Q
|
||||
q
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
7 0 obj
|
||||
<</Type/Font/BaseFont/Helvetica/Subtype/Type1/FontDescriptor 9 0 R/FirstChar 32/LastChar 251/Widths[278 278 355 556 556 889 667 222 333 333 389 584 278 333 278 278 556 556 556 556 556 556 556 556 556 556 278 278 584 584 584 556 1015 667 667 722 722 667 611 778 722 278 500 667 556 833 722 778 667 778 722 667 611 722 667 944 667 667 611 278 278 278 469 556 222 556 556 500 556 556 278 556 556 222 222 500 222 833 556 556 556 556 333 500 278 556 500 722 500 500 500 334 260 334 584 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 333 556 556 167 556 556 556 556 191 333 556 333 333 500 500 0 556 556 556 278 0 537 350 222 333 333 556 1000 1000 0 611 0 333 333 333 333 333 333 333 333 0 333 333 0 333 333 333 1000 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1000 0 370 0 0 0 0 556 778 1000 365 0 0 0 0 0 889 0 0 0 278 0 0 222 611 944 611]>>
|
||||
endobj
|
||||
8 0 obj
|
||||
[/ICCBased 10 0 R]
|
||||
endobj
|
||||
9 0 obj
|
||||
<</Type/FontDescriptor/FontName/Helvetica/Flags 32/FontBBox[-166 -225 1000 931]/ItalicAngle 0/Ascent 718/Descent -207/CapHeight 718/XHeight 523/StemV 88>>
|
||||
endobj
|
||||
10 0 obj
|
||||
<</Filter/FlateDecode/Length 361/N 3>>stream
|
||||
xœ…R±NA}-´³ÑXl)F<„„ÂJ &&H"v{Ëy<C38B>Ü—ÛC¤óÓük¿ÂÚ·#1„@œÍÜÌξyovs@é=6‰Ý©:°EOÊùî*ðp€#xÚØì,<2C>¼`«}Kû¨9®í¸<C3AD>V…Ö0¾ÑoM–€·GoÍ‹ŒyéšõC3Ö#æ÷ÌÏÛ<C38F>§-½2¿Kâ™Yò¸ öÃtÐwú zÐ 0Á!RÌ
|
||||
,X 0csWcL<63>Í<Þ±ðt1EƾœØcv)4PÇ%ZTR¸!SL…>Ϧäsù© v^ÁçJE)”Y"r…\–“„ì<E2809E>bŽÆ„Ȫ°v¹jè`ÈomVšÄÔäɈÍÉîøVµìR͈‚–;?3ûåNd:K×óÜÈëX™ÇçD*ZöúKE_n±<0B>»A•'´‡á£ZÿìS³ñ÷’«¶aŸ´;Ò_.KEÿ™Îõ*þ¿ýßFc˜
|
||||
endstream
|
||||
endobj
|
||||
xref
|
||||
0 11
|
||||
0000000000 65535 f
|
||||
0000000015 00000 n
|
||||
0000000082 00000 n
|
||||
0000000279 00000 n
|
||||
0000000330 00000 n
|
||||
0000002142 00000 n
|
||||
0000002298 00000 n
|
||||
0000003159 00000 n
|
||||
0000004021 00000 n
|
||||
0000004055 00000 n
|
||||
0000004225 00000 n
|
||||
trailer
|
||||
<</Root 1 0 R/Info 2 0 R/ID[<3dfb3504c34ede9fecfec1e3650b77b3><3dfb3504c34ede9fecfec1e3650b77b3>]/Size 11>>
|
||||
startxref
|
||||
4659
|
||||
%%EOF
|
||||
@@ -0,0 +1,94 @@
|
||||
%PDF-1.3
|
||||
%ßþø²
|
||||
1 0 obj
|
||||
<</Type/Catalog/Pages 3 0 R/Lang()/Metadata 4 0 R>>
|
||||
endobj
|
||||
2 0 obj
|
||||
<</Producer(http://bfo.com/products/publisher?version=alpha-work-39081M-20210205T100927)/CreationDate(D:20210205100940Z)/Subject(ICC test: XYZ)/ModDate(D:20210205100940Z)>>
|
||||
endobj
|
||||
3 0 obj
|
||||
<</Type/Pages/Kids[5 0 R]/Count 1>>
|
||||
endobj
|
||||
4 0 obj
|
||||
<</Type/Metadata/Subtype/XML/Length 1695>>stream
|
||||
<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?>
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description xmlns:pdf="http://ns.adobe.com/pdf/1.3/" rdf:about=""><pdf:Producer>http://bfo.com/products/publisher?version=alpha-work-39081M-20210205T100927</pdf:Producer></rdf:Description><rdf:Description xmlns:dc="http://purl.org/dc/elements/1.1/" rdf:about=""><dc:description><rdf:Alt><rdf:li xml:lang="x-default">ICC test: XYZ</rdf:li></rdf:Alt></dc:description><dc:format>application/pdf</dc:format></rdf:Description><rdf:Description xmlns:xmp="http://ns.adobe.com/xap/1.0/" rdf:about=""><xmp:CreateDate>2021-02-05T10:09:40Z</xmp:CreateDate><xmp:MetadataDate>2021-02-05T10:09:40Z</xmp:MetadataDate><xmp:ModifyDate>2021-02-05T10:09:40Z</xmp:ModifyDate></rdf:Description><rdf:Description xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#" xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" rdf:about=""><xmpMM:DerivedFrom><rdf:Description><stRef:lastModifyDate>2021-02-05T10:09:19Z</stRef:lastModifyDate><stRef:documentID>file:/Users/mike/bfo/trunk/bforeport2/icc-xyz.svg</stRef:documentID></rdf:Description></xmpMM:DerivedFrom><xmpMM:DocumentID>uuid:3e1ae994-d22f-a03a-62d6-d304c8c21c90</xmpMM:DocumentID><xmpMM:History><rdf:Seq><rdf:li><rdf:Description><stEvt:softwareAgent>BFO Publisher work-39081M-20210205T100927</stEvt:softwareAgent><stEvt:action>converted</stEvt:action><stEvt:when>2021-02-05T10:09:40Z</stEvt:when></rdf:Description></rdf:li></rdf:Seq></xmpMM:History><xmpMM:InstanceID>uuid:3e1ae994-d22f-a03a-62d6-d304c8c21c90</xmpMM:InstanceID></rdf:Description></rdf:RDF>
|
||||
<?xpacket end='r'?>
|
||||
endstream
|
||||
endobj
|
||||
5 0 obj
|
||||
<</Type/Page/MediaBox[0 0 150 150]/Resources<</ProcSet[/PDF/Text]/Font<</R1 7 0 R>>/ColorSpace<</R2 8 0 R>>>>/Contents[6 0 R]/Parent 3 0 R>>
|
||||
endobj
|
||||
6 0 obj
|
||||
<</Length 793>>stream
|
||||
0.75 0 0 0.75 0 0 cm
|
||||
q
|
||||
q
|
||||
Q
|
||||
BT
|
||||
0 0 0 1 k
|
||||
36.0415 185 Td
|
||||
/R1 9 Tf [(ICC pro®le of type=XYZ \(Matr)-14(ix\))4005]TJ
|
||||
-20.2835 -15 Td
|
||||
/R1 4 Tf [(https://github)40(.com/ellelstone/elles_icc_pro®les/b)20(lob/master/pro®les/XYZ-D50-Identity-elle-V4.icc)3939]TJ
|
||||
ET
|
||||
20 159.5 m 180 159.5 l 180 160 l 179.5 160 l 179.5 30 l 180 30 l 180 30.5 l 20 30.5 l 20 30 l 20.5 30 l 20.5 160 l 20 160 l h 19.5 160 m 19.5 30 l 19.5 29.5 l 20 29.5 l 180 29.5 l 180.5 29.5 l 180.5 30 l 180.5 160 l 180.5 160.5 l 180 160.5 l 20 160.5 l 19.5 160.5 l h f
|
||||
/R2 cs
|
||||
0 0 0 scn
|
||||
20 30 40 130 re f
|
||||
0 1 0 sc
|
||||
60 30 40 130 re f
|
||||
0 0.75 1 sc
|
||||
100 30 40 130 re f
|
||||
1 0.75 0 sc
|
||||
140 30 40 130 re f
|
||||
BT
|
||||
0 0 0 1 k
|
||||
36.72 15 Td
|
||||
[(y=0)9180]TJ
|
||||
40 0 Td
|
||||
[(y=1)19180]TJ
|
||||
29.548 0 Td
|
||||
[(x=0 y=0.75 z=1)26567]TJ
|
||||
40 0 Td
|
||||
[(x=1 y=0.75 z=0)36567]TJ
|
||||
ET
|
||||
Q
|
||||
q
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
7 0 obj
|
||||
<</Type/Font/BaseFont/Helvetica/Subtype/Type1/FontDescriptor 9 0 R/FirstChar 32/LastChar 251/Widths[278 278 355 556 556 889 667 222 333 333 389 584 278 333 278 278 556 556 556 556 556 556 556 556 556 556 278 278 584 584 584 556 1015 667 667 722 722 667 611 778 722 278 500 667 556 833 722 778 667 778 722 667 611 722 667 944 667 667 611 278 278 278 469 556 222 556 556 500 556 556 278 556 556 222 222 500 222 833 556 556 556 556 333 500 278 556 500 722 500 500 500 334 260 334 584 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 333 556 556 167 556 556 556 556 191 333 556 333 333 500 500 0 556 556 556 278 0 537 350 222 333 333 556 1000 1000 0 611 0 333 333 333 333 333 333 333 333 0 333 333 0 333 333 333 1000 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1000 0 370 0 0 0 0 556 778 1000 365 0 0 0 0 0 889 0 0 0 278 0 0 222 611 944 611]>>
|
||||
endobj
|
||||
8 0 obj
|
||||
[/ICCBased 10 0 R]
|
||||
endobj
|
||||
9 0 obj
|
||||
<</Type/FontDescriptor/FontName/Helvetica/Flags 32/FontBBox[-166 -225 1000 931]/ItalicAngle 0/Ascent 718/Descent -207/CapHeight 718/XHeight 523/StemV 88>>
|
||||
endobj
|
||||
10 0 obj
|
||||
<</Filter/FlateDecode/Length 359/N 3>>stream
|
||||
xœ…RÁJÃ@}I-zЛÅCŽVŒ‰-ôàɦ…Zh{K71-$iȦÖÞü4?A<ûž};)¥ÅY†y;ûæÍì²€ýžªLïù@8ÖÕ`8rŒï¡G8<47>*]\äÓì´ïOri®ÑÚÍÛjõ(ÖŠñ<C5A0>~¯Š²¬z{QÄö-óÇjFÄ<46>Ä—<C384>fÀiíWâ‡,<2C>«•Ž™à0Îû=áŸa€!Fp0E„9*¢
|
||||
KfƘ§Ü¹Œ9°]Ç:<15>.f(XW’›`Â*Mø¸F›<46>ÜQ)e=žÍ¨gð¹0+VÞÀãÊ¥S,³$ÔŠ¹4'‰Y;ÃWPŒ™
Qír¹x—ºfZäøŒ}êä–T7zë½ôª›’¡Üù™èW;“é4uLuB^Ês%¯£e<1E>-ÙQ³Ö[uôäv 3©è˜4xB3eóè§Vóï%×mË>ëR_«IÆÙüEX†ëüÿö?ÁêcŒ
|
||||
endstream
|
||||
endobj
|
||||
xref
|
||||
0 11
|
||||
0000000000 65535 f
|
||||
0000000015 00000 n
|
||||
0000000082 00000 n
|
||||
0000000270 00000 n
|
||||
0000000321 00000 n
|
||||
0000002092 00000 n
|
||||
0000002248 00000 n
|
||||
0000003090 00000 n
|
||||
0000003952 00000 n
|
||||
0000003986 00000 n
|
||||
0000004156 00000 n
|
||||
trailer
|
||||
<</Root 1 0 R/Info 2 0 R/ID[<3e1ae994d22fa03a62d6d304c8c21c90><3e1ae994d22fa03a62d6d304c8c21c90>]/Size 11>>
|
||||
startxref
|
||||
4588
|
||||
%%EOF
|
||||
Binary file not shown.
@@ -133,4 +133,28 @@
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="Integration\SpecificTestDocuments\icc-fogra55-7clr.pdf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="Integration\SpecificTestDocuments\icc-lab-8bit.pdf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="Integration\SpecificTestDocuments\icc-lab-test2.pdf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="Integration\SpecificTestDocuments\icc-lab2.pdf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="Integration\SpecificTestDocuments\icc-lab4.pdf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="Integration\SpecificTestDocuments\icc-xyz.pdf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="Integration\SpecificTestDocuments\xyztest.pdf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net45;net451;net452;net46;net461;net462;net47;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard2.0;net452;net46;net461;net462;net47;net6.0</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Version>0.1.8-alpha-001</Version>
|
||||
<IsTestProject>False</IsTestProject>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net45;net451;net452;net46;net461;net462;net47;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard2.0;net452;net46;net461;net462;net47;net6.0</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Version>0.1.8-alpha-001</Version>
|
||||
<IsTestProject>False</IsTestProject>
|
||||
|
||||
@@ -103,6 +103,18 @@
|
||||
return xyz;
|
||||
}
|
||||
|
||||
public (double R, double G, double B) TransformXYZToRGB((double X, double Y, double Z) xyz)
|
||||
{
|
||||
var adaptedColor = chromaticAdaptation.Transform(xyz);
|
||||
var rgb = transformationMatrix.Multiply(adaptedColor);
|
||||
|
||||
var gammaCorrectedR = destinationWorkingSpace.GammaCorrection(rgb.Item1);
|
||||
var gammaCorrectedG = destinationWorkingSpace.GammaCorrection(rgb.Item2);
|
||||
var gammaCorrectedB = destinationWorkingSpace.GammaCorrection(rgb.Item3);
|
||||
|
||||
return (Clamp(gammaCorrectedR), Clamp(gammaCorrectedG), Clamp(gammaCorrectedB));
|
||||
}
|
||||
|
||||
private static double Clamp(double value)
|
||||
{
|
||||
// Force value into range [0..1]
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
namespace UglyToad.PdfPig.Graphics.Colors
|
||||
{
|
||||
{
|
||||
using IccProfileNet;
|
||||
using IccProfileNet.Tags;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Tokens;
|
||||
using UglyToad.PdfPig.Content;
|
||||
using UglyToad.PdfPig.Functions;
|
||||
@@ -34,7 +37,7 @@
|
||||
/// <summary>
|
||||
/// The number of components for the underlying color space.
|
||||
/// </summary>
|
||||
public abstract int BaseNumberOfColorComponents { get; }
|
||||
internal abstract int BaseNumberOfColorComponents { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="ColorSpaceDetails"/>.
|
||||
@@ -90,7 +93,7 @@
|
||||
public override int NumberOfColorComponents => 1;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int BaseNumberOfColorComponents => NumberOfColorComponents;
|
||||
internal override int BaseNumberOfColorComponents => NumberOfColorComponents;
|
||||
|
||||
private DeviceGrayColorSpaceDetails() : base(ColorSpace.DeviceGray)
|
||||
{ }
|
||||
@@ -152,7 +155,7 @@
|
||||
public override int NumberOfColorComponents => 3;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int BaseNumberOfColorComponents => NumberOfColorComponents;
|
||||
internal override int BaseNumberOfColorComponents => NumberOfColorComponents;
|
||||
|
||||
private DeviceRgbColorSpaceDetails() : base(ColorSpace.DeviceRGB)
|
||||
{ }
|
||||
@@ -213,7 +216,7 @@
|
||||
public override int NumberOfColorComponents => 4;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int BaseNumberOfColorComponents => NumberOfColorComponents;
|
||||
internal override int BaseNumberOfColorComponents => NumberOfColorComponents;
|
||||
|
||||
private DeviceCmykColorSpaceDetails() : base(ColorSpace.DeviceCMYK)
|
||||
{
|
||||
@@ -287,9 +290,9 @@
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// <para>In the case of <see cref="IndexedColorSpaceDetails"/>, gets the <see cref="IndexedColorSpaceDetails.BaseColorSpaceDetails"/>' <c>BaseNumberOfColorComponents</c>.</para>
|
||||
/// <para>In the case of <see cref="IndexedColorSpaceDetails"/>, gets the <see cref="BaseColorSpaceDetails"/>' <c>BaseNumberOfColorComponents</c>.</para>
|
||||
/// </summary>
|
||||
public override int BaseNumberOfColorComponents => BaseColorSpaceDetails.BaseNumberOfColorComponents;
|
||||
internal override int BaseNumberOfColorComponents => BaseColorSpaceDetails.BaseNumberOfColorComponents;
|
||||
|
||||
/// <summary>
|
||||
/// The base color space in which the values in the color table are to be interpreted.
|
||||
@@ -317,7 +320,7 @@
|
||||
BaseColorSpaceDetails = baseColorSpaceDetails ?? throw new ArgumentNullException(nameof(baseColorSpaceDetails));
|
||||
HiVal = hiVal;
|
||||
ColorTable = colorTable;
|
||||
BaseType = baseColorSpaceDetails.BaseType;
|
||||
BaseType = baseColorSpaceDetails.Type;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -386,6 +389,7 @@
|
||||
multiplier = 1;
|
||||
break;
|
||||
|
||||
case ColorSpace.ICCBased:
|
||||
case ColorSpace.DeviceN:
|
||||
transformer = x =>
|
||||
{
|
||||
@@ -454,7 +458,7 @@
|
||||
public override int NumberOfColorComponents { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int BaseNumberOfColorComponents => AlternateColorSpaceDetails.NumberOfColorComponents;
|
||||
internal override int BaseNumberOfColorComponents => AlternateColorSpaceDetails.NumberOfColorComponents;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies name objects specifying the individual colour components. The length of the array shall
|
||||
@@ -504,12 +508,13 @@
|
||||
AlternateColorSpaceDetails = alternateColorSpaceDetails;
|
||||
Attributes = attributes;
|
||||
TintFunction = tintFunction;
|
||||
BaseType = alternateColorSpaceDetails.Type;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
internal override double[] Process(params double[] values)
|
||||
{
|
||||
var evaled = TintFunction.Eval(values[0]);
|
||||
var evaled = TintFunction.Eval(values);
|
||||
return AlternateColorSpaceDetails.Process(evaled);
|
||||
}
|
||||
|
||||
@@ -531,6 +536,89 @@
|
||||
/// <inheritdoc/>
|
||||
internal override IReadOnlyList<byte> Transform(IReadOnlyList<byte> decoded)
|
||||
{
|
||||
int outputCount = Process(Enumerable.Repeat(1.0, NumberOfColorComponents).ToArray()).Length;
|
||||
int outputSize = (int)(decoded.Count * outputCount / (double)NumberOfColorComponents);
|
||||
var transformed = new byte[outputSize];
|
||||
|
||||
Parallel.For(0, decoded.Count / NumberOfColorComponents, i =>
|
||||
{
|
||||
double[] comps = new double[NumberOfColorComponents];
|
||||
for (int n = 0; n < NumberOfColorComponents; n++)
|
||||
{
|
||||
comps[n] = decoded[i * NumberOfColorComponents + n] / 255.0;
|
||||
}
|
||||
|
||||
var colors = Process(comps);
|
||||
for (int c = 0; c < outputCount; c++)
|
||||
{
|
||||
transformed[i * outputCount + c] = ConvertToByte(colors[c]);
|
||||
}
|
||||
});
|
||||
return transformed;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
/// <inheritdoc/>
|
||||
internal override IReadOnlyList<byte> Transform(IReadOnlyList<byte> decoded)
|
||||
{
|
||||
int outputCount = Process(Enumerable.Repeat(1.0, NumberOfColorComponents).ToArray()).Length;
|
||||
int outputSize = (int)(decoded.Count * outputCount / (double)NumberOfColorComponents);
|
||||
var transformed = new byte[outputSize];
|
||||
|
||||
//Parallel.For(0, )
|
||||
for (var i = 0; i < decoded.Count / NumberOfColorComponents; i++)
|
||||
{
|
||||
double[] comps = new double[NumberOfColorComponents];
|
||||
for (int n = 0; n < NumberOfColorComponents; n++)
|
||||
{
|
||||
comps[n] = decoded[i* NumberOfColorComponents + n] / 255.0;
|
||||
}
|
||||
|
||||
var colors = Process(comps);
|
||||
for (int c = 0; c < outputCount; c++)
|
||||
{
|
||||
transformed[i * outputCount + c] = ConvertToByte(colors[c]);
|
||||
}
|
||||
}
|
||||
|
||||
return transformed;
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
/// <inheritdoc/>
|
||||
internal override IReadOnlyList<byte> Transform(IReadOnlyList<byte> decoded)
|
||||
{
|
||||
int outputCount = Process(Enumerable.Repeat(1.0, NumberOfColorComponents).ToArray()).Length;
|
||||
int outputSize = (int)(decoded.Count * outputCount / (double)NumberOfColorComponents);
|
||||
|
||||
var transformed = new byte[outputSize];
|
||||
for (var i = 0; i < decoded.Count; i += NumberOfColorComponents)
|
||||
{
|
||||
double[] comps = new double[NumberOfColorComponents];
|
||||
for (int n = 0; n < NumberOfColorComponents; n++)
|
||||
{
|
||||
comps[n] = decoded[i + n] / 255.0;
|
||||
}
|
||||
|
||||
var colors = Process(comps);
|
||||
for (int c = 0; c < outputCount; c++)
|
||||
{
|
||||
transformed[(i / NumberOfColorComponents) * outputCount + c] = ConvertToByte(colors[c]);
|
||||
}
|
||||
}
|
||||
|
||||
return transformed;
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
/// <inheritdoc/>
|
||||
internal override IReadOnlyList<byte> Transform(IReadOnlyList<byte> decoded)
|
||||
{
|
||||
int outputCount = Process(Enumerable.Repeat(1.0, NumberOfColorComponents).ToArray()).Length;
|
||||
|
||||
var transformed = new List<byte>();
|
||||
for (var i = 0; i < decoded.Count; i += NumberOfColorComponents)
|
||||
{
|
||||
@@ -541,7 +629,7 @@
|
||||
}
|
||||
|
||||
var colors = Process(comps);
|
||||
for (int c = 0; c < colors.Length; c++)
|
||||
for (int c = 0; c < outputCount; c++)
|
||||
{
|
||||
transformed.Add(ConvertToByte(colors[c]));
|
||||
}
|
||||
@@ -549,6 +637,7 @@
|
||||
|
||||
return transformed;
|
||||
}
|
||||
*/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override IColor GetInitializeColor()
|
||||
@@ -622,7 +711,7 @@
|
||||
public override int NumberOfColorComponents => 1;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int BaseNumberOfColorComponents => AlternateColorSpaceDetails.NumberOfColorComponents;
|
||||
internal override int BaseNumberOfColorComponents => AlternateColorSpaceDetails.NumberOfColorComponents;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the name of the colorant that this Separation color space is intended to represent.
|
||||
@@ -728,7 +817,7 @@
|
||||
public override int NumberOfColorComponents => 1;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int BaseNumberOfColorComponents => NumberOfColorComponents;
|
||||
internal override int BaseNumberOfColorComponents => NumberOfColorComponents;
|
||||
|
||||
private readonly CIEBasedColorSpaceTransformer colorSpaceTransformer;
|
||||
|
||||
@@ -852,7 +941,7 @@
|
||||
public override int NumberOfColorComponents => 3;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int BaseNumberOfColorComponents => NumberOfColorComponents;
|
||||
internal override int BaseNumberOfColorComponents => NumberOfColorComponents;
|
||||
|
||||
private readonly CIEBasedColorSpaceTransformer colorSpaceTransformer;
|
||||
|
||||
@@ -995,7 +1084,7 @@
|
||||
public override int NumberOfColorComponents => 3;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int BaseNumberOfColorComponents => NumberOfColorComponents;
|
||||
internal override int BaseNumberOfColorComponents => NumberOfColorComponents;
|
||||
|
||||
/// <summary>
|
||||
/// An array of three numbers [XW YW ZW] specifying the tristimulus value, in the CIE 1931 XYZ space of the
|
||||
@@ -1072,7 +1161,7 @@
|
||||
return transformed;
|
||||
}
|
||||
|
||||
private static double g(double x)
|
||||
internal static double g(double x)
|
||||
{
|
||||
if (x > 6.0 / 29.0)
|
||||
{
|
||||
@@ -1136,6 +1225,10 @@
|
||||
/// </summary>
|
||||
public sealed class ICCBasedColorSpaceDetails : ColorSpaceDetails
|
||||
{
|
||||
private readonly CIEBasedColorSpaceTransformer colorSpaceTransformer;
|
||||
|
||||
private readonly LabColorSpaceDetails labColorSpaceDetails;
|
||||
|
||||
/// <summary>
|
||||
/// The number of color components in the color space described by the ICC profile data.
|
||||
/// This numbers shall match the number of components actually in the ICC profile.
|
||||
@@ -1144,7 +1237,7 @@
|
||||
public override int NumberOfColorComponents { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int BaseNumberOfColorComponents => NumberOfColorComponents;
|
||||
internal override int BaseNumberOfColorComponents => NumberOfColorComponents;
|
||||
|
||||
/// <summary>
|
||||
/// An alternate color space that can be used in case the one specified in the stream data is not
|
||||
@@ -1176,11 +1269,17 @@
|
||||
[CanBeNull]
|
||||
public XmpMetadata Metadata { get; }
|
||||
|
||||
/// <summary>
|
||||
/// ICC profile.
|
||||
/// </summary>
|
||||
[CanBeNull]
|
||||
internal IccProfile Profile { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="ICCBasedColorSpaceDetails"/>.
|
||||
/// </summary>
|
||||
internal ICCBasedColorSpaceDetails(int numberOfColorComponents, [CanBeNull] ColorSpaceDetails alternateColorSpaceDetails,
|
||||
[CanBeNull] IReadOnlyList<decimal> range, [CanBeNull] XmpMetadata metadata)
|
||||
[CanBeNull] IReadOnlyList<decimal> range, [CanBeNull] XmpMetadata metadata, [CanBeNull] IReadOnlyList<byte> rawProfile)
|
||||
: base(ColorSpace.ICCBased)
|
||||
{
|
||||
if (numberOfColorComponents != 1 && numberOfColorComponents != 3 && numberOfColorComponents != 4)
|
||||
@@ -1201,13 +1300,78 @@
|
||||
throw new ArgumentOutOfRangeException(nameof(range), range,
|
||||
$"Must consist of exactly {2 * numberOfColorComponents} (2 x NumberOfColorComponents), but was passed {range.Count}");
|
||||
}
|
||||
Metadata = metadata;
|
||||
Metadata = metadata;
|
||||
|
||||
if (rawProfile != null)
|
||||
{
|
||||
System.IO.Directory.CreateDirectory("ICC_Profiles_errors");
|
||||
System.IO.File.WriteAllBytes($"ICC_Profiles_errors/ICC_{Guid.NewGuid().ToString().ToLower()}.icc",
|
||||
rawProfile.ToArray());
|
||||
|
||||
try
|
||||
{
|
||||
Profile = new IccProfile(rawProfile.ToArray());
|
||||
|
||||
if (Profile.Header.Pcs == IccProfileConnectionSpace.PCSXYZ)
|
||||
{
|
||||
IccXyz referenceWhite = Profile.Header.nCIEXYZ; // Really not sure
|
||||
colorSpaceTransformer = new CIEBasedColorSpaceTransformer((referenceWhite.X, referenceWhite.Y, referenceWhite.Z), RGBWorkingSpace.sRGB);
|
||||
}
|
||||
else // LAB
|
||||
{
|
||||
if (Profile.Tags.TryGetValue(IccTags.MediaWhitePointTag, out var tag) && tag is IccXyzType whitepoint)
|
||||
{
|
||||
IccXyz referenceWhite = Profile.Header.nCIEXYZ; // Really not sure
|
||||
labColorSpaceDetails = new LabColorSpaceDetails(new decimal[]
|
||||
{
|
||||
(decimal)referenceWhite.X, (decimal)referenceWhite.Y, (decimal)referenceWhite.Z
|
||||
}, null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"ERROR creating ICC profile: {ex}");
|
||||
|
||||
System.IO.Directory.CreateDirectory("ICC_Profiles_errors");
|
||||
System.IO.File.WriteAllBytes($"ICC_Profiles_errors/ICC_{Guid.NewGuid().ToString().ToLower()}.icc",
|
||||
rawProfile.ToArray());
|
||||
//throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
internal override double[] Process(params double[] values)
|
||||
{
|
||||
// TODO - use ICC profile
|
||||
if (Profile != null)
|
||||
{
|
||||
if (Profile.Header.ColourSpace == IccColourSpaceType.CMYK && values[0] == 0 && values[1] == 0 && values[2] == 0 && values[3] == 1)
|
||||
{
|
||||
// See 'COLOR MANAGEMENT UNDERSTANDING AND USING ICC PROFILES' by Phil Green:
|
||||
// Issues in CMYK Workflows
|
||||
// Pure black (0–0–0–K) turns into four-color C–M–Y–K color build, with resulting color shift,
|
||||
// misregister, and / or trap implications. Also known as the black type problem.
|
||||
return new double[] { 0, 0, 0 }; // Black RGB
|
||||
}
|
||||
|
||||
if (Profile.TryProcessToPcs(values, null, out double[] xyz) && xyz.Length == 3) // No rendering intent for now
|
||||
{
|
||||
double x = xyz[0];
|
||||
double y = xyz[1];
|
||||
double z = xyz[2];
|
||||
|
||||
if (Profile.Header.Pcs == IccProfileConnectionSpace.PCSXYZ)
|
||||
{
|
||||
var rgb = colorSpaceTransformer.TransformXYZToRGB((x, y, z));
|
||||
return new double[] { rgb.R, rgb.G, rgb.B };
|
||||
}
|
||||
else
|
||||
{
|
||||
return labColorSpaceDetails.Process(x * 100.0, y * 255.0 - 127.0, z * 255.0 - 127.0); // need to scale
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return AlternateColorSpaceDetails.Process(values);
|
||||
}
|
||||
@@ -1220,7 +1384,71 @@
|
||||
throw new ArgumentException($"Invalid number of imputs, expecting {NumberOfColorComponents} but got {values.Length}", nameof(values));
|
||||
}
|
||||
|
||||
// TODO - use ICC profile
|
||||
if (Profile != null)
|
||||
{
|
||||
if (Profile.Header.ColourSpace == IccColourSpaceType.CMYK && values[0] == 0 && values[1] == 0 && values[2] == 0 && values[3] == 1)
|
||||
{
|
||||
// See 'COLOR MANAGEMENT UNDERSTANDING AND USING ICC PROFILES' by Phil Green:
|
||||
// Issues in CMYK Workflows
|
||||
// Pure black (0–0–0–K) turns into four-color C–M–Y–K color build, with resulting color shift,
|
||||
// misregister, and / or trap implications. Also known as the black type problem.
|
||||
return RGBColor.Black;
|
||||
}
|
||||
|
||||
if (values.Any(x => x > 1.0))
|
||||
{
|
||||
//values[0] /= 100.0;
|
||||
//values[1] = (values[1] + 127.0) / 255.0;
|
||||
//values[2] = (values[2] + 127.0) / 255.0;
|
||||
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
values[i] /= 100.0;
|
||||
}
|
||||
}
|
||||
|
||||
if (Profile.Header.ColourSpace == IccColourSpaceType.CIELABorPCSLAB)
|
||||
{
|
||||
//values[0] /= 100.0;
|
||||
//values[1] = (values[1] + 127.0) / 256.0;
|
||||
//values[2] = (values[2] + 127.0) / 256.0;
|
||||
|
||||
// Component Ranges: L*: [0 100]; a* and b*: [−128 127]
|
||||
//double b = PdfFunction.ClipToRange(values[1], -128.0, 127.0);
|
||||
//double c = PdfFunction.ClipToRange(values[2], -128.0, 127.0);
|
||||
|
||||
//double M = (values[0] + 16.0) / 116.0;
|
||||
//double L = M + (b / 500.0);
|
||||
//double N = M - (c / 200.0);
|
||||
|
||||
//IccXyz referenceWhite = Profile.Header.nCIEXYZ;
|
||||
|
||||
//values[0] = LabColorSpaceDetails.g(L) * referenceWhite.X; // X
|
||||
//values[1] = LabColorSpaceDetails.g(M) * referenceWhite.Y; // Y
|
||||
//values[2] = LabColorSpaceDetails.g(N) * referenceWhite.Z; // Z
|
||||
|
||||
//var labColor = labColorSpaceDetails.GetColor(values[0], values[1], values[2]);
|
||||
//values[0] =
|
||||
}
|
||||
|
||||
|
||||
if (Profile.TryProcessToPcs(values, null, out double[] xyz) && xyz.Length == 3) // No rendering intent for now
|
||||
{
|
||||
double x = xyz[0];
|
||||
double y = xyz[1];
|
||||
double z = xyz[2];
|
||||
|
||||
if (Profile.Header.Pcs == IccProfileConnectionSpace.PCSXYZ)
|
||||
{
|
||||
var (R, G, B) = colorSpaceTransformer.TransformXYZToRGB((x, y, z));
|
||||
return new RGBColor((decimal)R, (decimal)G, (decimal)B);
|
||||
}
|
||||
else
|
||||
{
|
||||
return labColorSpaceDetails.GetColor(x * 100.0, y * 255.0 - 127.0, z * 255.0 - 127.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return AlternateColorSpaceDetails.GetColor(values);
|
||||
}
|
||||
@@ -1240,7 +1468,30 @@
|
||||
/// <inheritdoc/>
|
||||
internal override IReadOnlyList<byte> Transform(IReadOnlyList<byte> decoded)
|
||||
{
|
||||
// TODO - use ICC profile
|
||||
if (Profile != null)
|
||||
{
|
||||
int outputCount = Process(Enumerable.Repeat(1.0, NumberOfColorComponents).ToArray()).Length;
|
||||
int outputSize = (int)(decoded.Count * outputCount / (double)NumberOfColorComponents);
|
||||
var transformed = new byte[outputSize];
|
||||
|
||||
//Parallel.For(0, decoded.Count / NumberOfColorComponents, i =>
|
||||
for (int i = 0; i < decoded.Count / NumberOfColorComponents; i++)
|
||||
{
|
||||
double[] comps = new double[NumberOfColorComponents];
|
||||
for (int n = 0; n < NumberOfColorComponents; n++)
|
||||
{
|
||||
comps[n] = decoded[i * NumberOfColorComponents + n] / 255.0;
|
||||
}
|
||||
|
||||
var colors = Process(comps);
|
||||
for (int c = 0; c < outputCount; c++)
|
||||
{
|
||||
transformed[i * outputCount + c] = ConvertToByte(colors[c]);
|
||||
}
|
||||
}
|
||||
//);
|
||||
return transformed;
|
||||
}
|
||||
|
||||
return AlternateColorSpaceDetails.Transform(decoded);
|
||||
}
|
||||
@@ -1270,7 +1521,7 @@
|
||||
/// Cannot be called for <see cref="UnsupportedColorSpaceDetails"/>, will throw a <see cref="InvalidOperationException"/>.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public override int BaseNumberOfColorComponents => NumberOfColorComponents;
|
||||
internal override int BaseNumberOfColorComponents => NumberOfColorComponents;
|
||||
|
||||
private UnsupportedColorSpaceDetails() : base(ColorSpace.DeviceGray)
|
||||
{
|
||||
@@ -1300,4 +1551,4 @@
|
||||
throw new InvalidOperationException("UnsupportedColorSpaceDetails");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
370
src/UglyToad.PdfPig/Graphics/Colors/ICC/IccHelper.cs
Normal file
370
src/UglyToad.PdfPig/Graphics/Colors/ICC/IccHelper.cs
Normal file
@@ -0,0 +1,370 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace IccProfileNet
|
||||
{
|
||||
/// <summary>
|
||||
/// ICC
|
||||
/// </summary>
|
||||
internal static class IccHelper
|
||||
{
|
||||
internal static double[] Lookup(double[] input, double[][] clut, byte[] clutGridPoints)
|
||||
{
|
||||
// https://stackoverflow.com/questions/35109195/how-do-the-the-different-parts-of-an-icc-file-work-together
|
||||
if (input.Length != clutGridPoints.Length)
|
||||
{
|
||||
throw new ArgumentException("TODO");
|
||||
}
|
||||
|
||||
// TODO - Need interpolation
|
||||
double index = 0;
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
double w = input[i] * clutGridPoints[i];
|
||||
for (int j = i + 1; j < input.Length; j++)
|
||||
{
|
||||
w *= clutGridPoints[j];
|
||||
}
|
||||
index += w;
|
||||
}
|
||||
|
||||
int indexInt = (int)index;
|
||||
if (indexInt > clut.Length - 1)
|
||||
{
|
||||
//indexInt = clut.Length - 1;
|
||||
}
|
||||
|
||||
return clut[indexInt];
|
||||
}
|
||||
|
||||
internal static double[] Lookup(double[] input, double[][] clut, int clutGridPoints)
|
||||
{
|
||||
// https://stackoverflow.com/questions/35109195/how-do-the-the-different-parts-of-an-icc-file-work-together
|
||||
|
||||
// TODO - Need interpolation
|
||||
double index = 0;
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
double pow = Math.Pow(clutGridPoints, input.Length - i);
|
||||
index += input[i] * pow;
|
||||
}
|
||||
|
||||
int indexInt = (int)index;
|
||||
if (indexInt > clut.Length - 1)
|
||||
{
|
||||
//indexInt = indexInt % clut.Length;
|
||||
}
|
||||
|
||||
return clut[indexInt];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TODO
|
||||
/// </summary>
|
||||
/// <param name="profileBytes"></param>
|
||||
/// <returns></returns>
|
||||
public static byte[] ComputeProfileId(byte[] profileBytes)
|
||||
{
|
||||
// Compute profile id
|
||||
// This field, if not zero (00h), shall hold the Profile ID. The Profile ID shall be calculated using the MD5
|
||||
// fingerprinting method as defined in Internet RFC 1321.The entire profile, whose length is given by the size field
|
||||
// in the header, with the profile flags field (bytes 44 to 47, see 7.2.11), rendering intent field (bytes 64 to 67, see
|
||||
// 7.2.15), and profile ID field (bytes 84 to 99) in the profile header temporarily set to zeros (00h), shall be used to
|
||||
// calculate the ID. A profile ID field value of zero (00h) shall indicate that a profile ID has not been calculated.
|
||||
// Profile creators should compute and record a profile ID.
|
||||
|
||||
// with the profile flags field (bytes 44 to 47, see 7.2.11)
|
||||
for (int i = IccProfileHeader.ProfileFlagsOffset; i < IccProfileHeader.ProfileFlagsOffset + IccProfileHeader.ProfileFlagsLength; i++)
|
||||
{
|
||||
profileBytes[i] = 0;
|
||||
}
|
||||
|
||||
// rendering intent field (bytes 64 to 67, see 7.2.15)
|
||||
for (int i = IccProfileHeader.RenderingIntentOffset; i < IccProfileHeader.RenderingIntentOffset + IccProfileHeader.RenderingIntentLength; i++)
|
||||
{
|
||||
profileBytes[i] = 0;
|
||||
}
|
||||
|
||||
for (int i = IccProfileHeader.ProfileIdOffset; i < IccProfileHeader.ProfileIdOffset + IccProfileHeader.ProfileIdLength; i++)
|
||||
{
|
||||
profileBytes[i] = 0;
|
||||
}
|
||||
|
||||
using (var md5 = System.Security.Cryptography.MD5.Create())
|
||||
{
|
||||
return md5.ComputeHash(profileBytes);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string GetString(byte[] bytes, int index, int count)
|
||||
{
|
||||
return Encoding.ASCII.GetString(bytes, index, count).Replace("\0", string.Empty);
|
||||
}
|
||||
|
||||
internal static string GetString(byte[] bytes)
|
||||
{
|
||||
return Encoding.ASCII.GetString(bytes).Replace("\0", string.Empty);
|
||||
}
|
||||
|
||||
internal static string GetHexadecimalString(byte[] bytes, int startIndex, int length)
|
||||
{
|
||||
return BitConverter.ToString(bytes, startIndex, length).Replace("-", string.Empty);
|
||||
}
|
||||
|
||||
internal static string GetHexadecimalString(byte[] bytes)
|
||||
{
|
||||
return BitConverter.ToString(bytes).Replace("-", string.Empty);
|
||||
}
|
||||
|
||||
internal static IccXyz ReadXyz(byte[] bytes)
|
||||
{
|
||||
var xyz = Reads15Fixed16Array(bytes);
|
||||
return new IccXyz(xyz[0], xyz[1], xyz[2]);
|
||||
}
|
||||
|
||||
internal static DateTime? ReadDateTime(byte[] bytes)
|
||||
{
|
||||
if (bytes.All(b => b == 0))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
Array.Reverse(bytes);
|
||||
|
||||
var secondsL = BitConverter.ToUInt16(bytes, 0);
|
||||
var minutesL = BitConverter.ToUInt16(bytes, 2);
|
||||
var hoursL = BitConverter.ToUInt16(bytes, 4);
|
||||
var dayL = BitConverter.ToUInt16(bytes, 6);
|
||||
var monthL = BitConverter.ToUInt16(bytes, 8);
|
||||
var yearL = BitConverter.ToUInt16(bytes, 10);
|
||||
return new DateTime(yearL, monthL, dayL, hoursL, minutesL, secondsL);
|
||||
}
|
||||
|
||||
var year = BitConverter.ToUInt16(bytes, 0);
|
||||
var month = BitConverter.ToUInt16(bytes, 2);
|
||||
var day = BitConverter.ToUInt16(bytes, 4);
|
||||
var hours = BitConverter.ToUInt16(bytes, 6);
|
||||
var minutes = BitConverter.ToUInt16(bytes, 8);
|
||||
var seconds = BitConverter.ToUInt16(bytes, 10);
|
||||
return new DateTime(year, month, day, hours, minutes, seconds);
|
||||
}
|
||||
|
||||
internal const int S15Fixed16Length = 4;
|
||||
|
||||
internal static double ReadU8Fixed8Number(byte[] bytes)
|
||||
{
|
||||
// TODO - use ReadUInt16 instead of BitConverter.ToInt16 ????
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
Array.Reverse(bytes);
|
||||
}
|
||||
|
||||
return Math.Round(BitConverter.ToInt16(bytes, 0) / ((double)byte.MaxValue + 1), 4, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
|
||||
internal static double Reads15Fixed16Number(byte[] bytes)
|
||||
{
|
||||
// TODO - use ReadUInt32 instead of BitConverter.ToInt32 ????
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
Array.Reverse(bytes);
|
||||
}
|
||||
|
||||
return Math.Round((BitConverter.ToInt32(bytes, 0) - 0.5f) / ((double)ushort.MaxValue + 1), 4, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
|
||||
internal static double[] Reads15Fixed16Array(byte[] bytes)
|
||||
{
|
||||
if (bytes.Length % 4 != 0)
|
||||
{
|
||||
throw new ArgumentException("Array should be of length that is a multiple of 4", nameof(bytes));
|
||||
}
|
||||
|
||||
double[] values = new double[bytes.Length / S15Fixed16Length];
|
||||
for (int e = 0; e < values.Length; e++)
|
||||
{
|
||||
byte[] a = bytes.Skip(e * S15Fixed16Length).Take(S15Fixed16Length).ToArray();
|
||||
values[e] = Reads15Fixed16Number(a);
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
internal static uint ReadUInt32(byte[] bytes)
|
||||
{
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
Array.Reverse(bytes);
|
||||
}
|
||||
|
||||
return BitConverter.ToUInt32(bytes, 0);
|
||||
}
|
||||
|
||||
internal const int UInt16Length = 2;
|
||||
|
||||
internal static ushort ReadUInt16(byte[] bytes)
|
||||
{
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
Array.Reverse(bytes);
|
||||
}
|
||||
|
||||
return BitConverter.ToUInt16(bytes, 0);
|
||||
}
|
||||
|
||||
internal static byte ReadUInt8(byte[] bytes)
|
||||
{
|
||||
return bytes[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Each curve and processing element shall start on a 4-byte boundary.
|
||||
/// <para>
|
||||
/// To achieve this, each item shall be followed by up to three 00h pad bytes as needed.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
internal static int AdjustOffsetTo4ByteBoundary(int offset)
|
||||
{
|
||||
return offset + (offset % 4);
|
||||
}
|
||||
|
||||
internal static IccPrimaryPlatforms GetPrimaryPlatforms(string platform)
|
||||
{
|
||||
switch (platform)
|
||||
{
|
||||
case "APPL":
|
||||
return IccPrimaryPlatforms.AppleComputer;
|
||||
|
||||
case "MSFT":
|
||||
return IccPrimaryPlatforms.MicrosoftCorporation;
|
||||
|
||||
case "SGI ":
|
||||
return IccPrimaryPlatforms.SiliconGraphics;
|
||||
|
||||
case "SUNW":
|
||||
return IccPrimaryPlatforms.SunMicrosystems;
|
||||
|
||||
default:
|
||||
return IccPrimaryPlatforms.Unidentified;
|
||||
}
|
||||
}
|
||||
|
||||
internal static IccColourSpaceType GetColourSpaceType(string colourSpace)
|
||||
{
|
||||
switch (colourSpace)
|
||||
{
|
||||
case "XYZ ":
|
||||
return IccColourSpaceType.nCIEXYZorPCSXYZ;
|
||||
|
||||
case "Lab ":
|
||||
return IccColourSpaceType.CIELABorPCSLAB;
|
||||
|
||||
case "Luv ":
|
||||
return IccColourSpaceType.CIELUV;
|
||||
|
||||
case "YCbr":
|
||||
return IccColourSpaceType.YCbCr;
|
||||
|
||||
case "Yxy ":
|
||||
return IccColourSpaceType.CIEYxy;
|
||||
|
||||
case "RGB ":
|
||||
return IccColourSpaceType.RGB;
|
||||
|
||||
case "GRAY":
|
||||
return IccColourSpaceType.Gray;
|
||||
|
||||
case "HSV ":
|
||||
return IccColourSpaceType.HSV;
|
||||
|
||||
case "HLS ":
|
||||
return IccColourSpaceType.HLS;
|
||||
|
||||
case "CMYK":
|
||||
return IccColourSpaceType.CMYK;
|
||||
|
||||
case "CMY ":
|
||||
return IccColourSpaceType.CMY;
|
||||
|
||||
case "2CLR":
|
||||
return IccColourSpaceType.Colour2;
|
||||
|
||||
case "3CLR":
|
||||
return IccColourSpaceType.Colour3;
|
||||
|
||||
case "4CLR":
|
||||
return IccColourSpaceType.Colour4;
|
||||
|
||||
case "5CLR":
|
||||
return IccColourSpaceType.Colour5;
|
||||
|
||||
case "6CLR":
|
||||
return IccColourSpaceType.Colour6;
|
||||
|
||||
case "7CLR":
|
||||
return IccColourSpaceType.Colour7;
|
||||
|
||||
case "8CLR":
|
||||
return IccColourSpaceType.Colour8;
|
||||
|
||||
case "9CLR":
|
||||
return IccColourSpaceType.Colour9;
|
||||
|
||||
case "ACLR":
|
||||
return IccColourSpaceType.Colour10;
|
||||
|
||||
case "BCLR":
|
||||
return IccColourSpaceType.Colour11;
|
||||
|
||||
case "CCLR":
|
||||
return IccColourSpaceType.Colour12;
|
||||
|
||||
case "DCLR":
|
||||
return IccColourSpaceType.Colour13;
|
||||
|
||||
case "ECLR":
|
||||
return IccColourSpaceType.Colour14;
|
||||
|
||||
case "FCLR":
|
||||
return IccColourSpaceType.Colour15;
|
||||
|
||||
default:
|
||||
throw new ArgumentException($"Unknown colour space type '{colourSpace}'.");
|
||||
}
|
||||
}
|
||||
|
||||
internal static IccProfileClass GetProfileClass(string profile)
|
||||
{
|
||||
switch (profile)
|
||||
{
|
||||
case "scnr":
|
||||
return IccProfileClass.Input;
|
||||
|
||||
case "mntr":
|
||||
return IccProfileClass.Display;
|
||||
|
||||
case "prtr":
|
||||
return IccProfileClass.Output;
|
||||
|
||||
case "link":
|
||||
return IccProfileClass.DeviceLink;
|
||||
|
||||
case "spac":
|
||||
return IccProfileClass.ColorSpace;
|
||||
|
||||
case "abst":
|
||||
return IccProfileClass.Abstract;
|
||||
|
||||
case "nmcl":
|
||||
return IccProfileClass.NamedColor;
|
||||
|
||||
default:
|
||||
throw new ArgumentException($"Unknown profile class '{profile}'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
396
src/UglyToad.PdfPig/Graphics/Colors/ICC/IccProfile.cs
Normal file
396
src/UglyToad.PdfPig/Graphics/Colors/ICC/IccProfile.cs
Normal file
@@ -0,0 +1,396 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using IccProfileNet.Parsers;
|
||||
using IccProfileNet.Tags;
|
||||
|
||||
namespace IccProfileNet
|
||||
{
|
||||
/// <summary>
|
||||
/// ICC profile.
|
||||
/// </summary>
|
||||
internal class IccProfile
|
||||
{
|
||||
/// <summary>
|
||||
/// ICC profile header.
|
||||
/// </summary>
|
||||
public IccProfileHeader Header { get; }
|
||||
|
||||
private readonly Lazy<IccTagTableItem[]> tagTable;
|
||||
/// <summary>
|
||||
/// The tag table acts as a table of contents for the tags and an index into the tag data element in the profiles.
|
||||
/// </summary>
|
||||
public IccTagTableItem[] TagTable => tagTable.Value;
|
||||
|
||||
private readonly Lazy<IReadOnlyDictionary<string, IccTagTypeBase>> tags;
|
||||
public IReadOnlyDictionary<string, IccTagTypeBase> Tags => tags.Value;
|
||||
|
||||
/// <summary>
|
||||
/// TODO
|
||||
/// </summary>
|
||||
public byte[] Data { get; }
|
||||
|
||||
/// <summary>
|
||||
/// ICC profile v4.
|
||||
/// </summary>
|
||||
public IccProfile(byte[] data)
|
||||
{
|
||||
Data = data;
|
||||
Header = new IccProfileHeader(data);
|
||||
tagTable = new Lazy<IccTagTableItem[]>(() => ParseTagTable(data.Skip(128).ToArray()));
|
||||
tags = new Lazy<IReadOnlyDictionary<string, IccTagTypeBase>>(() => GetTags());
|
||||
}
|
||||
|
||||
public byte[] GetOrComputeProfileId()
|
||||
{
|
||||
if (Header.IsProfileIdComputed())
|
||||
{
|
||||
return Header.ProfileId;
|
||||
}
|
||||
|
||||
return ComputeProfileId();
|
||||
}
|
||||
|
||||
public byte[] ComputeProfileId()
|
||||
{
|
||||
return IccHelper.ComputeProfileId(Data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the profile against the profile id.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the profile id contained in the header matches the re-computed profile id.
|
||||
/// <c>false</c> if they don't match.
|
||||
/// <c>null</c> if the profile id is not set in the header.
|
||||
/// </returns>
|
||||
public bool? ValidateProfile()
|
||||
{
|
||||
if (Header.IsProfileIdComputed())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return Header.ProfileId.SequenceEqual(ComputeProfileId());
|
||||
}
|
||||
|
||||
private static IccTagTableItem[] ParseTagTable(byte[] bytes)
|
||||
{
|
||||
// Tag count (n)
|
||||
// 0 to 3
|
||||
uint tagCount = IccHelper.ReadUInt32(bytes
|
||||
.Skip(IccTagTableItem.TagCountOffset)
|
||||
.Take(IccTagTableItem.TagCountLength).ToArray());
|
||||
|
||||
IccTagTableItem[] tagTableItems = new IccTagTableItem[tagCount];
|
||||
|
||||
for (var i = 0; i < tagCount; ++i)
|
||||
{
|
||||
int currentOffset = i * (IccTagTableItem.TagSignatureLength +
|
||||
IccTagTableItem.TagOffsetLength +
|
||||
IccTagTableItem.TagSizeLength);
|
||||
|
||||
// Tag Signature
|
||||
// 4 to 7
|
||||
string signature = IccHelper.GetString(bytes,
|
||||
currentOffset + IccTagTableItem.TagSignatureOffset, IccTagTableItem.TagSignatureLength);
|
||||
|
||||
// Offset to beginning of tag data element
|
||||
// 8 to 11
|
||||
uint offset = IccHelper.ReadUInt32(bytes
|
||||
.Skip(currentOffset + IccTagTableItem.TagOffsetOffset)
|
||||
.Take(IccTagTableItem.TagOffsetLength).ToArray());
|
||||
|
||||
// Size of tag data element
|
||||
// 12 to 15
|
||||
uint size = IccHelper.ReadUInt32(bytes
|
||||
.Skip(currentOffset + IccTagTableItem.TagSizeOffset)
|
||||
.Take(IccTagTableItem.TagSizeLength).ToArray());
|
||||
|
||||
tagTableItems[i] = new IccTagTableItem(signature, offset, size);
|
||||
}
|
||||
|
||||
return tagTableItems;
|
||||
}
|
||||
|
||||
private IReadOnlyDictionary<string, IccTagTypeBase> GetTags()
|
||||
{
|
||||
switch (Header.VersionMajor)
|
||||
{
|
||||
case 4:
|
||||
{
|
||||
var tags = new Dictionary<string, IccTagTypeBase>();
|
||||
for (int t = 0; t < TagTable.Length; t++)
|
||||
{
|
||||
IccTagTableItem tag = TagTable[t];
|
||||
tags.Add(tag.Signature, IccProfileV4TagParser.Parse(Data, tag));
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
case 2:
|
||||
{
|
||||
var tags = new Dictionary<string, IccTagTypeBase>();
|
||||
for (int t = 0; t < TagTable.Length; t++)
|
||||
{
|
||||
IccTagTableItem tag = TagTable[t];
|
||||
tags.Add(tag.Signature, IccProfileV2TagParser.Parse(Data, tag));
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new NotImplementedException($"ICC Profile v{Header.VersionMajor}.{Header.VersionMinor} is not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryProcessGrayTRC(double[] input, out double[] output)
|
||||
{
|
||||
// 8.3.4 Monochrome Input profiles
|
||||
// 8.4.4 Monochrome Display profiles
|
||||
// 8.5.3 Monochrome Output profiles
|
||||
if (Tags.TryGetValue(IccTags.GrayTRCTag, out var kTrcTag) && kTrcTag is IccBaseCurveType kTrc)
|
||||
{
|
||||
double v = kTrc.Process(input.Single());
|
||||
output = new double[] { v, v, v };
|
||||
return true;
|
||||
}
|
||||
|
||||
output = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryProcessTRCMatrix(double[] input, out double[] output)
|
||||
{
|
||||
// 8.3.3 Three-component matrix-based Input profiles
|
||||
// 8.4.3 Three-component matrix-based Display profiles
|
||||
// See p197 of Wiley book
|
||||
if (Tags.TryGetValue(IccTags.RedMatrixColumnTag, out var rmcTag) && rmcTag is IccXyzType rmc &&
|
||||
Tags.TryGetValue(IccTags.GreenMatrixColumnTag, out var gmcTag) && gmcTag is IccXyzType gmc &&
|
||||
Tags.TryGetValue(IccTags.BlueMatrixColumnTag, out var bmcTag) && bmcTag is IccXyzType bmc &&
|
||||
Tags.TryGetValue(IccTags.RedTRCTag, out var rTrcTag) && rTrcTag is IccBaseCurveType rTrc &&
|
||||
Tags.TryGetValue(IccTags.GreenTRCTag, out var gTrcTag) && gTrcTag is IccBaseCurveType gTrc &&
|
||||
Tags.TryGetValue(IccTags.BlueTRCTag, out var bTrcTag) && bTrcTag is IccBaseCurveType bTrc)
|
||||
{
|
||||
// Optional
|
||||
// Tags.TryGetValue(IccTags.ChromaticAdaptationTag, out var caTag) && caTag is IccS15Fixed16ArrayType ca
|
||||
|
||||
double channel1 = input[0];
|
||||
double channel2 = input[1];
|
||||
double channel3 = input[2];
|
||||
|
||||
double lR = rTrc.Process(channel1);
|
||||
double lG = gTrc.Process(channel2);
|
||||
double lB = bTrc.Process(channel3);
|
||||
|
||||
double cX = (rmc.Xyz.X * lR) + (gmc.Xyz.X * lG) + (bmc.Xyz.X * lB);
|
||||
double cY = (rmc.Xyz.Y * lR) + (gmc.Xyz.Y * lG) + (bmc.Xyz.Y * lB);
|
||||
double cZ = (rmc.Xyz.Z * lR) + (gmc.Xyz.Z * lG) + (bmc.Xyz.Z * lB);
|
||||
|
||||
output = new double[] { cX, cY, cZ };
|
||||
return true;
|
||||
}
|
||||
|
||||
output = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process from Device space to PCS.
|
||||
/// <para>
|
||||
/// A to B.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public bool TryProcessToPcs(double[] input, IccRenderingIntent? renderingIntent, out double[] output)
|
||||
{
|
||||
// See Table 25 — Profile type/profile tag and defined rendering intents
|
||||
|
||||
//try
|
||||
//{
|
||||
if (TryProcessGrayTRC(input, out output))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (TryProcessTRCMatrix(input, out output))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (renderingIntent == null)
|
||||
{
|
||||
// use profile rendering intent
|
||||
renderingIntent = Header.RenderingIntent;
|
||||
}
|
||||
|
||||
switch (Header.ProfileClass)
|
||||
{
|
||||
case IccProfileClass.Input:
|
||||
case IccProfileClass.Display:
|
||||
case IccProfileClass.Output:
|
||||
case IccProfileClass.ColorSpace:
|
||||
{
|
||||
// 8.3.2 N-component LUT-based Input profiles
|
||||
// 8.4.2 N-Component LUT-based Display profiles
|
||||
// 8.5.2 N-component LUT-based Output profiles
|
||||
|
||||
if (renderingIntent == IccRenderingIntent.Perceptual && Tags.TryGetValue(IccTags.AToB0Tag, out var lutAB0Tag)
|
||||
&& lutAB0Tag is IIccClutType lutAb0)
|
||||
{
|
||||
output = lutAb0.Process(input, Header);
|
||||
return true;
|
||||
}
|
||||
else if (renderingIntent == IccRenderingIntent.MediaRelativeColorimetric && Tags.TryGetValue(IccTags.AToB1Tag, out var lutAB1Tag)
|
||||
&& lutAB1Tag is IIccClutType lutAb1)
|
||||
{
|
||||
output = lutAb1.Process(input, Header);
|
||||
return true;
|
||||
}
|
||||
else if (renderingIntent == IccRenderingIntent.Saturation && Tags.TryGetValue(IccTags.AToB2Tag, out var lutAB2Tag)
|
||||
&& lutAB2Tag is IIccClutType lutAb2)
|
||||
{
|
||||
output = lutAb2.Process(input, Header);
|
||||
return true;
|
||||
}
|
||||
else if (Tags.TryGetValue(IccTags.AToB0Tag, out var lutAB0TagDefault) && lutAB0TagDefault is IIccClutType lutAb0Default)
|
||||
{
|
||||
output = lutAb0Default.Process(input, Header);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case IccProfileClass.Abstract:
|
||||
case IccProfileClass.DeviceLink:
|
||||
case IccProfileClass.NamedColor: // undefined actually
|
||||
{
|
||||
// TODO - use IIccClutType instead?
|
||||
if (Tags.TryGetValue(IccTags.AToB0Tag, out var lutAB0Tag) && lutAB0Tag is IIccClutType lutAb0)
|
||||
{
|
||||
output = lutAb0.Process(input, Header);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException("TODO");
|
||||
}
|
||||
//}
|
||||
//catch (Exception ex)
|
||||
//{
|
||||
// // Ignore
|
||||
// System.Diagnostics.Debug.WriteLine(ex);
|
||||
// output = null;
|
||||
// return false;
|
||||
//}
|
||||
|
||||
output = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process from PCS to Device space.
|
||||
/// <para>
|
||||
/// B to A.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public bool TryProcessFromPcs(double[] input, IccRenderingIntent? renderingIntent, out double[] output)
|
||||
{
|
||||
// TODO
|
||||
|
||||
// See Table 25 — Profile type/profile tag and defined rendering intents
|
||||
|
||||
try
|
||||
{
|
||||
if (TryProcessGrayTRC(input, out output)) // TODO - Need inversion
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (TryProcessTRCMatrix(input, out output)) // TODO - Need inversion
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (renderingIntent == null)
|
||||
{
|
||||
// use profile rendering intent
|
||||
renderingIntent = Header.RenderingIntent;
|
||||
}
|
||||
|
||||
switch (Header.ProfileClass)
|
||||
{
|
||||
case IccProfileClass.Input:
|
||||
case IccProfileClass.Display:
|
||||
case IccProfileClass.Output:
|
||||
case IccProfileClass.ColorSpace:
|
||||
{
|
||||
// 8.3.2 N-component LUT-based Input profiles
|
||||
// 8.4.2 N-Component LUT-based Display profiles
|
||||
// 8.5.2 N-component LUT-based Output profiles
|
||||
if (Tags.TryGetValue(IccTags.BToA0Tag, out var lutBA0Tag) && lutBA0Tag is IIccClutType lutBa0 &&
|
||||
Tags.TryGetValue(IccTags.BToA1Tag, out var lutBA1Tag) && lutBA1Tag is IIccClutType lutBa1 &&
|
||||
Tags.TryGetValue(IccTags.BToA2Tag, out var lutBA2Tag) && lutBA2Tag is IIccClutType lutBa2)
|
||||
{
|
||||
// Optional??
|
||||
// Tags.TryGetValue(IccTags.GamutTag, out var gamutTag)
|
||||
// Tags.TryGetValue(IccTags.ColorantTableTag, out var colorantTableTag)
|
||||
|
||||
switch (renderingIntent)
|
||||
{
|
||||
case IccRenderingIntent.Perceptual:
|
||||
output = lutBa0.Process(input, Header);
|
||||
return true;
|
||||
|
||||
case IccRenderingIntent.MediaRelativeColorimetric:
|
||||
output = lutBa1.Process(input, Header);
|
||||
return true;
|
||||
|
||||
case IccRenderingIntent.Saturation:
|
||||
output = lutBa2.Process(input, Header);
|
||||
return true;
|
||||
|
||||
default:
|
||||
output = lutBa0.Process(input, Header);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case IccProfileClass.Abstract:
|
||||
case IccProfileClass.DeviceLink:
|
||||
case IccProfileClass.NamedColor: // undefined actually
|
||||
{
|
||||
if (Tags.TryGetValue(IccTags.BToA0Tag, out var lutBA0Tag) && lutBA0Tag is IccLutABType lutBa0)
|
||||
{
|
||||
output = lutBa0.Process(input, Header);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException("TODO");
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Ignore
|
||||
output = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
output = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"ICC Profile v{Header}";
|
||||
}
|
||||
}
|
||||
}
|
||||
563
src/UglyToad.PdfPig/Graphics/Colors/ICC/IccProfileHeader.cs
Normal file
563
src/UglyToad.PdfPig/Graphics/Colors/ICC/IccProfileHeader.cs
Normal file
@@ -0,0 +1,563 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace IccProfileNet
|
||||
{
|
||||
/// <summary>
|
||||
/// ICC profile header.
|
||||
/// </summary>
|
||||
internal struct IccProfileHeader
|
||||
{
|
||||
#region ICC Profile header constants
|
||||
public const int ProfileSizeOffset = 0;
|
||||
public const int ProfileSizeLength = 4;
|
||||
public const int CmmOffset = 4;
|
||||
public const int CmmLength = 4;
|
||||
public const int VersionOffset = 8;
|
||||
public const int VersionLength = 4;
|
||||
public const int ProfileClassOffset = 12;
|
||||
public const int ProfileClassLength = 4;
|
||||
public const int ColourSpaceOffset = 16;
|
||||
public const int ColourSpaceLength = 4;
|
||||
public const int PcsByteOffset = 20;
|
||||
public const int PcsByteLength = 4;
|
||||
public const int CreatedOffset = 24;
|
||||
public const int CreatedLength = 12;
|
||||
public const int ProfileSignatureOffset = 36;
|
||||
public const int ProfileSignatureLength = 4;
|
||||
public const int PrimaryPlatformSignatureOffset = 40;
|
||||
public const int PrimaryPlatformSignatureLength = 4;
|
||||
public const int ProfileFlagsOffset = 44;
|
||||
public const int ProfileFlagsLength = 4;
|
||||
public const int DeviceManufacturerOffset = 48;
|
||||
public const int DeviceManufacturerLength = 4;
|
||||
public const int DeviceModelOffset = 52;
|
||||
public const int DeviceModelLength = 4;
|
||||
public const int DeviceAttributesOffset = 56;
|
||||
public const int DeviceAttributesLength = 8;
|
||||
public const int RenderingIntentOffset = 64;
|
||||
public const int RenderingIntentLength = 4;
|
||||
public const int nCIEXYZOffset = 68;
|
||||
public const int nCIEXYZLength = 12;
|
||||
public const int ProfileCreatorSignatureOffset = 80;
|
||||
public const int ProfileCreatorSignatureLength = 4;
|
||||
public const int ProfileIdOffset = 84;
|
||||
public const int ProfileIdLength = 16;
|
||||
#endregion
|
||||
|
||||
private readonly Lazy<uint> profileSize;
|
||||
/// <summary>
|
||||
/// Profile size.
|
||||
/// </summary>
|
||||
public uint ProfileSize => profileSize.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Profile major version.
|
||||
/// </summary>
|
||||
public int VersionMajor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Profile minor version.
|
||||
/// </summary>
|
||||
public int VersionMinor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Profile bug fix version.
|
||||
/// </summary>
|
||||
public int VersionBugFix { get; }
|
||||
|
||||
private readonly Lazy<string> cmm;
|
||||
/// <summary>
|
||||
/// Preferred CMM type.
|
||||
/// </summary>
|
||||
public string Cmm => cmm.Value;
|
||||
|
||||
private readonly Lazy<IccProfileClass> profileClass;
|
||||
/// <summary>
|
||||
/// Profile/Device class.
|
||||
/// </summary>
|
||||
public IccProfileClass ProfileClass => profileClass.Value;
|
||||
|
||||
private readonly Lazy<IccColourSpaceType> colourSpace;
|
||||
/// <summary>
|
||||
/// Colour space of data (possibly a derived space).
|
||||
/// </summary>
|
||||
public IccColourSpaceType ColourSpace => colourSpace.Value;
|
||||
|
||||
private readonly Lazy<IccProfileConnectionSpace> pcs;
|
||||
/// <summary>
|
||||
/// Profile connection space. An abstract color space used to connect the source and destination profiles.
|
||||
/// </summary>
|
||||
public IccProfileConnectionSpace Pcs => pcs.Value;
|
||||
|
||||
private readonly Lazy<DateTime?> created;
|
||||
/// <summary>
|
||||
/// Date and time this profile was first created.
|
||||
/// </summary>
|
||||
public DateTime? Created => created.Value;
|
||||
|
||||
private readonly Lazy<string> profileSignature;
|
||||
/// <summary>
|
||||
/// profile file signature.
|
||||
/// </summary>
|
||||
public string ProfileSignature => profileSignature.Value;
|
||||
|
||||
private readonly Lazy<IccPrimaryPlatforms> primaryPlatformSignature;
|
||||
/// <summary>
|
||||
/// Primary platform signature.
|
||||
/// </summary>
|
||||
public IccPrimaryPlatforms PrimaryPlatformSignature => primaryPlatformSignature.Value;
|
||||
|
||||
private readonly Lazy<byte[]> profileFlags;
|
||||
/// <summary>
|
||||
/// Profile flags to indicate various options for the CMM such as distributed
|
||||
/// processing and caching options.
|
||||
/// </summary>
|
||||
public byte[] ProfileFlags => profileFlags.Value;
|
||||
|
||||
private readonly Lazy<string> deviceManufacturer;
|
||||
/// <summary>
|
||||
/// Device manufacturer of the device for which this profile is created.
|
||||
/// </summary>
|
||||
public string DeviceManufacturer => deviceManufacturer.Value;
|
||||
|
||||
private readonly Lazy<string> deviceModel;
|
||||
/// <summary>
|
||||
/// Device model of the device for which this profile is created.
|
||||
/// </summary>
|
||||
public string DeviceModel => deviceModel.Value;
|
||||
|
||||
private readonly Lazy<byte[]> deviceAttributes;
|
||||
/// <summary>
|
||||
/// Device attributes unique to the particular device setup such as media type.
|
||||
/// </summary>
|
||||
public byte[] DeviceAttributes => deviceAttributes.Value;
|
||||
|
||||
private readonly Lazy<IccRenderingIntent> renderingIntent;
|
||||
/// <summary>
|
||||
/// A particular gamut mapping style or method of converting colors in one gamut to colors in another gamut.
|
||||
/// </summary>
|
||||
public IccRenderingIntent RenderingIntent => renderingIntent.Value;
|
||||
|
||||
private readonly Lazy<IccXyz> nciexyz;
|
||||
/// <summary>
|
||||
/// The nCIEXYZ values of the illuminant of the PCS.
|
||||
/// </summary>
|
||||
#pragma warning disable IDE1006 // Naming Styles
|
||||
public IccXyz nCIEXYZ => nciexyz.Value;
|
||||
#pragma warning restore IDE1006 // Naming Styles
|
||||
|
||||
private readonly Lazy<string> profileCreatorSignature;
|
||||
/// <summary>
|
||||
/// Profile creator signature.
|
||||
/// </summary>
|
||||
public string ProfileCreatorSignature => profileCreatorSignature.Value;
|
||||
|
||||
private readonly Lazy<byte[]> profileId;
|
||||
/// <summary>
|
||||
/// Profile ID.
|
||||
/// </summary>
|
||||
public byte[] ProfileId => profileId.Value;
|
||||
|
||||
public bool IsProfileIdComputed()
|
||||
{
|
||||
return !ProfileId.All(v => v == 0);
|
||||
}
|
||||
|
||||
public IccProfileHeader(byte[] profile)
|
||||
{
|
||||
// Profile version number
|
||||
// 8 to 11
|
||||
/*
|
||||
* The profile version with which the profile is compliant shall be encoded as binary-coded decimal in the profile
|
||||
* version field. The first byte (byte 8) shall identify the major version and byte 9 shall identify the minor version
|
||||
* and bug fix version in each 4-bit half of the byte. Bytes 10 and 11 are reserved and shall be set to zero. The
|
||||
* major and minor versions are set by the International Color Consortium. The profile version number consistent
|
||||
* with this ICC specification is “4.4.0.0” (encoded as 04400000h)
|
||||
*/
|
||||
var profileVersionNumber = profile.Skip(VersionOffset).Take(VersionLength).ToArray();
|
||||
VersionMajor = profileVersionNumber[0];
|
||||
VersionMinor = (int)((uint)(profileVersionNumber[1] & 0xf0) >> 4);
|
||||
VersionBugFix = (int)(uint)(profileVersionNumber[1] & 0x0f);
|
||||
|
||||
profileSize = new Lazy<uint>(() =>
|
||||
{
|
||||
// Profile size
|
||||
// 0 to 3 - UInt32Number
|
||||
return IccHelper.ReadUInt32(profile.Skip(ProfileSizeOffset).Take(ProfileSizeLength).ToArray());
|
||||
});
|
||||
|
||||
cmm = new Lazy<string>(() =>
|
||||
{
|
||||
// Preferred CMM type
|
||||
// 4 to 7
|
||||
return IccHelper.GetString(profile, CmmOffset, CmmLength);
|
||||
});
|
||||
|
||||
profileClass = new Lazy<IccProfileClass>(() =>
|
||||
{
|
||||
// Profile/Device class
|
||||
// 12 to 15
|
||||
return IccHelper.GetProfileClass(IccHelper.GetString(profile, ProfileClassOffset, ProfileClassLength));
|
||||
});
|
||||
|
||||
colourSpace = new Lazy<IccColourSpaceType>(() =>
|
||||
{
|
||||
// Colour space of data (possibly a derived space)
|
||||
// 16 to 19
|
||||
return IccHelper.GetColourSpaceType(IccHelper.GetString(profile, ColourSpaceOffset, ColourSpaceLength));
|
||||
});
|
||||
|
||||
pcs = new Lazy<IccProfileConnectionSpace>(() =>
|
||||
{
|
||||
// PCS
|
||||
// 20 to 23
|
||||
string pcs = IccHelper.GetString(profile, PcsByteOffset, PcsByteLength).Trim();
|
||||
switch (pcs)
|
||||
{
|
||||
case "Lab":
|
||||
return IccProfileConnectionSpace.PCSLAB;
|
||||
|
||||
case "XYZ":
|
||||
return IccProfileConnectionSpace.PCSXYZ;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException($"Invalid PCS value '{pcs}'. Expecting 'Lab' or 'XYZ'.");
|
||||
}
|
||||
});
|
||||
|
||||
created = new Lazy<DateTime?>(() =>
|
||||
{
|
||||
// Date and time this profile was first created
|
||||
// 24 to 35 - dateTimeNumber
|
||||
return IccHelper.ReadDateTime(profile.Skip(CreatedOffset).Take(CreatedLength).ToArray());
|
||||
});
|
||||
|
||||
profileSignature = new Lazy<string>(() =>
|
||||
{
|
||||
// ‘acsp’ (61637370h) profile file signature
|
||||
// 36 to 39
|
||||
// The profile file signature field shall contain the value “acsp” (61637370h) as a profile file signature.
|
||||
return IccHelper.GetString(profile, ProfileSignatureOffset, ProfileSignatureLength);
|
||||
});
|
||||
|
||||
primaryPlatformSignature = new Lazy<IccPrimaryPlatforms>(() =>
|
||||
{
|
||||
// Primary platform signature
|
||||
// 40 to 43
|
||||
return IccHelper.GetPrimaryPlatforms(IccHelper.GetString(profile, PrimaryPlatformSignatureOffset, PrimaryPlatformSignatureLength));
|
||||
});
|
||||
|
||||
profileFlags = new Lazy<byte[]>(() =>
|
||||
{
|
||||
// Profile flags to indicate various options for the CMM such as distributed
|
||||
// processing and caching options
|
||||
// 44 to 47
|
||||
return profile.Skip(ProfileFlagsOffset).Take(ProfileFlagsLength).ToArray();// TODO
|
||||
});
|
||||
|
||||
deviceManufacturer = new Lazy<string>(() =>
|
||||
{
|
||||
// Device manufacturer of the device for which this profile is created
|
||||
// 48 to 51
|
||||
return IccHelper.GetString(profile, DeviceManufacturerOffset, DeviceManufacturerLength);
|
||||
});
|
||||
|
||||
deviceModel = new Lazy<string>(() =>
|
||||
{
|
||||
// Device model of the device for which this profile is created
|
||||
// 52 to 55
|
||||
return IccHelper.GetString(profile, DeviceModelOffset, DeviceModelLength);
|
||||
});
|
||||
|
||||
deviceAttributes = new Lazy<byte[]>(() =>
|
||||
{
|
||||
// Device attributes unique to the particular device setup such as media type
|
||||
// 56 to 63
|
||||
return profile.Skip(DeviceAttributesOffset).Take(DeviceAttributesLength).ToArray(); // TODO
|
||||
});
|
||||
|
||||
renderingIntent = new Lazy<IccRenderingIntent>(() =>
|
||||
{
|
||||
// Rendering Intent
|
||||
// 64 to 67
|
||||
return (IccRenderingIntent)IccHelper.ReadUInt32(profile.Skip(RenderingIntentOffset).Take(RenderingIntentLength).ToArray());
|
||||
});
|
||||
|
||||
nciexyz = new Lazy<IccXyz>(() =>
|
||||
{
|
||||
// The nCIEXYZ values of the illuminant of the PCS
|
||||
// 68 to 79 - XYZNumber
|
||||
// shall be X = 0,964 2, Y = 1,0 and Z = 0,824 9
|
||||
// These values are the nCIEXYZ values of CIE illuminant D50
|
||||
return IccHelper.ReadXyz(profile.Skip(nCIEXYZOffset).Take(nCIEXYZLength).ToArray());
|
||||
});
|
||||
|
||||
profileCreatorSignature = new Lazy<string>(() =>
|
||||
{
|
||||
// Profile creator signature
|
||||
// 80 to 83
|
||||
return IccHelper.GetString(profile, ProfileCreatorSignatureOffset, ProfileCreatorSignatureLength);
|
||||
});
|
||||
|
||||
profileId = new Lazy<byte[]>(() =>
|
||||
{
|
||||
// Profile ID
|
||||
// 84 to 99
|
||||
return profile.Skip(ProfileIdOffset).Take(ProfileIdLength).ToArray();
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{VersionMajor}.{VersionMinor}.{VersionBugFix}";
|
||||
}
|
||||
}
|
||||
|
||||
internal enum IccProfileConnectionSpace
|
||||
{
|
||||
PCSXYZ,
|
||||
PCSLAB
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// There are three basic classes of device profiles, which are Input, Display and Output. In addition to the three
|
||||
/// basic device profile classes, four additional colour processing profiles are defined. These profiles provide a
|
||||
/// standard implementation for use by the CMM in general colour processing, or for the convenience of CMMs
|
||||
/// which may use these types to store calculated transforms.These four additional profile classes are DeviceLink,
|
||||
/// ColorSpace, Abstract and NamedColor.
|
||||
/// </summary>
|
||||
public enum IccProfileClass
|
||||
{
|
||||
/// <summary>
|
||||
/// Input device profile (‘scnr’).
|
||||
/// </summary>
|
||||
Input,
|
||||
|
||||
/// <summary>
|
||||
/// Display device profile (‘mntr’).
|
||||
/// </summary>
|
||||
Display,
|
||||
|
||||
/// <summary>
|
||||
/// Output device profile (‘prtr’).
|
||||
/// </summary>
|
||||
Output,
|
||||
|
||||
/// <summary>
|
||||
/// DeviceLink profile (‘link’).
|
||||
/// </summary>
|
||||
DeviceLink,
|
||||
|
||||
/// <summary>
|
||||
/// ColorSpace profile (‘spac’).
|
||||
/// </summary>
|
||||
ColorSpace,
|
||||
|
||||
/// <summary>
|
||||
/// Abstract profile (‘abst’).
|
||||
/// </summary>
|
||||
Abstract,
|
||||
|
||||
/// <summary>
|
||||
/// NamedColor profile (‘nmcl’).
|
||||
/// </summary>
|
||||
NamedColor
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This field shall contain the signature of the data colour space expected on the A side (device side) of the profile
|
||||
/// transforms. The names and signatures of the permitted data colour spaces are shown in Table 19. Signatures
|
||||
/// are left justified.
|
||||
/// </summary>
|
||||
public enum IccColourSpaceType
|
||||
{
|
||||
/// <summary>
|
||||
/// nCIEXYZ or PCSXYZ.
|
||||
/// </summary>
|
||||
nCIEXYZorPCSXYZ,
|
||||
|
||||
/// <summary>
|
||||
/// CIELAB or PCSLAB.
|
||||
/// </summary>
|
||||
CIELABorPCSLAB,
|
||||
|
||||
/// <summary>
|
||||
/// CIELUV.
|
||||
/// </summary>
|
||||
CIELUV,
|
||||
|
||||
/// <summary>
|
||||
/// YCbCr.
|
||||
/// </summary>
|
||||
YCbCr,
|
||||
|
||||
/// <summary>
|
||||
/// CIEYxy.
|
||||
/// </summary>
|
||||
CIEYxy,
|
||||
|
||||
/// <summary>
|
||||
/// RGB.
|
||||
/// </summary>
|
||||
RGB,
|
||||
|
||||
/// <summary>
|
||||
/// Gray.
|
||||
/// </summary>
|
||||
Gray,
|
||||
|
||||
/// <summary>
|
||||
/// HSV.
|
||||
/// </summary>
|
||||
HSV,
|
||||
|
||||
/// <summary>
|
||||
/// HLS.
|
||||
/// </summary>
|
||||
HLS,
|
||||
|
||||
/// <summary>
|
||||
/// CMYK.
|
||||
/// </summary>
|
||||
CMYK,
|
||||
|
||||
/// <summary>
|
||||
/// CMY.
|
||||
/// </summary>
|
||||
CMY,
|
||||
|
||||
/// <summary>
|
||||
/// 2 colour.
|
||||
/// </summary>
|
||||
Colour2,
|
||||
|
||||
/// <summary>
|
||||
/// 3 colour (other than those listed above).
|
||||
/// </summary>
|
||||
Colour3,
|
||||
|
||||
/// <summary>
|
||||
/// 4 colour (other than CMYK).
|
||||
/// </summary>
|
||||
Colour4,
|
||||
|
||||
/// <summary>
|
||||
/// 5 colour.
|
||||
/// </summary>
|
||||
Colour5,
|
||||
|
||||
/// <summary>
|
||||
/// 6 colour.
|
||||
/// </summary>
|
||||
Colour6,
|
||||
|
||||
/// <summary>
|
||||
/// 7 colour.
|
||||
/// </summary>
|
||||
Colour7,
|
||||
|
||||
/// <summary>
|
||||
/// 8 colour.
|
||||
/// </summary>
|
||||
Colour8,
|
||||
|
||||
/// <summary>
|
||||
/// 9 colour.
|
||||
/// </summary>
|
||||
Colour9,
|
||||
|
||||
/// <summary>
|
||||
/// 10 colour.
|
||||
/// </summary>
|
||||
Colour10,
|
||||
|
||||
/// <summary>
|
||||
/// 11 colour.
|
||||
/// </summary>
|
||||
Colour11,
|
||||
|
||||
/// <summary>
|
||||
/// 12 colour.
|
||||
/// </summary>
|
||||
Colour12,
|
||||
|
||||
/// <summary>
|
||||
/// 13 colour.
|
||||
/// </summary>
|
||||
Colour13,
|
||||
|
||||
/// <summary>
|
||||
/// 14 colour.
|
||||
/// </summary>
|
||||
Colour14,
|
||||
|
||||
/// <summary>
|
||||
/// 15 colour.
|
||||
/// </summary>
|
||||
Colour15,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The rendering intent field shall specify the rendering intent which should be used (or, in the case of a DeviceLink
|
||||
/// profile, was used) when this profile is (was) combined with another profile. In a sequence of more than two
|
||||
/// profiles, it applies to the combination of this profile and the next profile in the sequence and not to the entire
|
||||
/// sequence. Typically, the user or application will set the rendering intent dynamically at runtime or embedding
|
||||
/// time. Therefore, this flag may not have any meaning until the profile is used in some context, e.g. in a DeviceLink
|
||||
/// or an embedded source profile
|
||||
/// </summary>
|
||||
public enum IccRenderingIntent : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// Perceptual.
|
||||
/// </summary>
|
||||
Perceptual = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Media-relative colorimetric.
|
||||
/// </summary>
|
||||
MediaRelativeColorimetric = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Saturation.
|
||||
/// </summary>
|
||||
Saturation = 2,
|
||||
|
||||
/// <summary>
|
||||
/// ICC-absolute colorimetric.
|
||||
/// </summary>
|
||||
IccAbsoluteColorimetric = 3
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identify the primary platform/operating system framework for which the profile was created.
|
||||
/// </summary>
|
||||
public enum IccPrimaryPlatforms
|
||||
{
|
||||
/// <summary>
|
||||
/// f there is no primary platform identified, this field shall be set to zero (00000000h)
|
||||
/// </summary>
|
||||
Unidentified,
|
||||
|
||||
/// <summary>
|
||||
/// Apple Computer, Inc.
|
||||
/// </summary>
|
||||
AppleComputer,
|
||||
|
||||
/// <summary>
|
||||
/// Microsoft Corporation.
|
||||
/// </summary>
|
||||
MicrosoftCorporation,
|
||||
|
||||
/// <summary>
|
||||
/// Silicon Graphics, Inc.
|
||||
/// </summary>
|
||||
SiliconGraphics,
|
||||
|
||||
/// <summary>
|
||||
/// Sun Microsystems, Inc.
|
||||
/// </summary>
|
||||
SunMicrosystems
|
||||
}
|
||||
}
|
||||
47
src/UglyToad.PdfPig/Graphics/Colors/ICC/IccTagTableItem.cs
Normal file
47
src/UglyToad.PdfPig/Graphics/Colors/ICC/IccTagTableItem.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
namespace IccProfileNet
|
||||
{
|
||||
internal readonly struct IccTagTableItem
|
||||
{
|
||||
#region ICC Profile tags constants
|
||||
public const int TagCountOffset = 0;
|
||||
public const int TagCountLength = 4;
|
||||
public const int TagSignatureOffset = 4;
|
||||
public const int TagSignatureLength = 4;
|
||||
public const int TagOffsetOffset = 8;
|
||||
public const int TagOffsetLength = 4;
|
||||
public const int TagSizeOffset = 12;
|
||||
public const int TagSizeLength = 4;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Tag Signature.
|
||||
/// </summary>
|
||||
public string Signature { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Offset to beginning of tag data element.
|
||||
/// </summary>
|
||||
public uint Offset { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Size of tag data element.
|
||||
/// </summary>
|
||||
public uint Size { get; }
|
||||
|
||||
/// <summary>
|
||||
/// TODO
|
||||
/// </summary>
|
||||
public IccTagTableItem(string signature, uint offset, uint size)
|
||||
{
|
||||
Signature = signature;
|
||||
Offset = offset;
|
||||
Size = size;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Signature}: offset={Offset}, size={Size}";
|
||||
}
|
||||
}
|
||||
}
|
||||
83
src/UglyToad.PdfPig/Graphics/Colors/ICC/IccTags.cs
Normal file
83
src/UglyToad.PdfPig/Graphics/Colors/ICC/IccTags.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
namespace IccProfileNet
|
||||
{
|
||||
internal static class IccTags
|
||||
{
|
||||
// v4.4 and v2.4 tags
|
||||
public const string DeviceModelDescTag = "dmdd";
|
||||
public const string DeviceMfgDescTag = "dmnd";
|
||||
public const string GamutTag = "gamt";
|
||||
public const string GreenTRCTag = "gTRC";
|
||||
public const string GrayTRCTag = "kTRC";
|
||||
public const string LuminanceTag = "lumi";
|
||||
public const string MeasurementTag = "meas";
|
||||
public const string NamedColor2Tag = "ncl2";
|
||||
public const string Preview0Tag = "pre0";
|
||||
public const string Preview1Tag = "pre1";
|
||||
public const string Preview2Tag = "pre2";
|
||||
public const string ProfileSequenceDescTag = "pseq";
|
||||
public const string OutputResponseTag = "resp";
|
||||
public const string RedTRCTag = "rTRC";
|
||||
public const string CharTargetTag = "targ";
|
||||
public const string TechnologyTag = "tech";
|
||||
public const string ViewingConditionsTag = "view";
|
||||
public const string ViewingCondDescTag = "vued";
|
||||
public const string MediaWhitePointTag = "wtpt";
|
||||
public const string AToB0Tag = "A2B0";
|
||||
public const string AToB1Tag = "A2B1";
|
||||
public const string AToB2Tag = "A2B2";
|
||||
public const string BToA0Tag = "B2A0";
|
||||
public const string BToA1Tag = "B2A1";
|
||||
public const string BToA2Tag = "B2A2";
|
||||
public const string BlueTRCTag = "bTRC";
|
||||
public const string CalibrationDateTimeTag = "calt";
|
||||
public const string ChromaticAdaptationTag = "chad";
|
||||
public const string ChromaticityTag = "chrm";
|
||||
public const string CopyrightTag = "cprt";
|
||||
public const string ProfileDescriptionTag = "desc";
|
||||
|
||||
// v4.4 and v2.4 tags with different names
|
||||
// v4.4 names
|
||||
public const string BlueMatrixColumnTag = "bXYZ";
|
||||
public const string GreenMatrixColumnTag = "gXYZ";
|
||||
public const string RedMatrixColumnTag = "rXYZ";
|
||||
|
||||
// v2.4 names
|
||||
public const string BlueColorantTag = "bXYZ";
|
||||
public const string GreenColorantTag = "gXYZ";
|
||||
public const string RedColorantTag = "rXYZ";
|
||||
|
||||
// v4.4 only tags
|
||||
public const string MetadataTag = "meta";
|
||||
public const string ProfileSequenceIdentifierTag = "psid";
|
||||
public const string PerceptualRenderingIntentGamutTag = "rig0";
|
||||
public const string SaturationRenderingIntentGamutTag = "rig2";
|
||||
public const string BToD0Tag = "B2D0";
|
||||
public const string BToD1Tag = "B2D1";
|
||||
public const string BToD2Tag = "B2D2";
|
||||
public const string BToD3Tag = "B2D3";
|
||||
public const string CicpTag = "cicp";
|
||||
public const string ColorimetricIntentImageStateTag = "ciis";
|
||||
public const string ColorantTableOutTag = "clot";
|
||||
public const string ColorantOrderTag = "clro";
|
||||
public const string ColorantTableTag = "clrt";
|
||||
public const string DToB0Tag = "D2B0";
|
||||
public const string DToB1Tag = "D2B1";
|
||||
public const string DToB2Tag = "D2B2";
|
||||
public const string DToB3Tag = "D2B3";
|
||||
|
||||
// v2.4 only tags
|
||||
public const string NamedColorTag = "ncol";
|
||||
public const string Ps2RenderingIntentTag = "ps2i";
|
||||
public const string Ps2CSATag = "ps2s";
|
||||
public const string Ps2CRD0Tag = "psd0";
|
||||
public const string Ps2CRD1Tag = "psd1";
|
||||
public const string Ps2CRD2Tag = "psd2";
|
||||
public const string Ps2CRD3Tag = "psd3";
|
||||
public const string ScreeningDescTag = "scrd";
|
||||
public const string ScreeningTag = "scrn";
|
||||
public const string UcrbgTag = "bfd ";
|
||||
public const string MediaBlackPointTag = "bkpt";
|
||||
public const string CrdInfoTag = "crdi";
|
||||
public const string DeviceSettingsTag = "devs";
|
||||
}
|
||||
}
|
||||
33
src/UglyToad.PdfPig/Graphics/Colors/ICC/IccXyz.cs
Normal file
33
src/UglyToad.PdfPig/Graphics/Colors/ICC/IccXyz.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
namespace IccProfileNet
|
||||
{
|
||||
internal struct IccXyz
|
||||
{
|
||||
/// <summary>
|
||||
/// X.
|
||||
/// </summary>
|
||||
public double X { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Y.
|
||||
/// </summary>
|
||||
public double Y { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Z.
|
||||
/// </summary>
|
||||
public double Z { get; }
|
||||
|
||||
public IccXyz(double x, double y, double z)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
Z = z;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{X}, {Y}, {Z}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using IccProfileNet.Tags;
|
||||
|
||||
namespace IccProfileNet.Parsers
|
||||
{
|
||||
internal static class IccProfileV2TagParser
|
||||
{
|
||||
/// <summary>
|
||||
/// The profile version number consistent with this ICC specification is “2.4.0.0”.
|
||||
/// <para>TODO - update with correct parsers.</para>
|
||||
/// </summary>
|
||||
public static IccTagTypeBase Parse(byte[] profile, IccTagTableItem tag)
|
||||
{
|
||||
byte[] data = profile.Skip((int)tag.Offset).Take((int)tag.Size).ToArray();
|
||||
switch (tag.Signature)
|
||||
{
|
||||
case IccTags.AToB0Tag: // 6.4.1 AToB0Tag
|
||||
case IccTags.AToB1Tag: // 6.4.2 AToB1Tag
|
||||
case IccTags.AToB2Tag: // 6.4.3 AToB2Tag
|
||||
case IccTags.BToA0Tag: // 6.4.6 BToA0Tag
|
||||
case IccTags.BToA1Tag: // 6.4.7 BToA1Tag
|
||||
case IccTags.BToA2Tag: // 6.4.8 BToA2Tag
|
||||
case IccTags.GamutTag: // 6.4.18 gamutTag
|
||||
case IccTags.Preview0Tag: // 6.4.29 preview0Tag
|
||||
case IccTags.Preview1Tag: // 6.4.30 preview1Tag
|
||||
case IccTags.Preview2Tag: // 6.4.31 preview2Tag
|
||||
return IccBaseLutType.Parse(data); // Tag Type: lut8Type or lut16Type
|
||||
|
||||
case IccTags.BlueColorantTag: // 6.4.4 blueColorantTag
|
||||
case IccTags.GreenColorantTag: // 6.4.20 greenColorantTag
|
||||
case IccTags.LuminanceTag: // 6.4.22 luminanceTag
|
||||
case IccTags.MediaBlackPointTag: // 6.4.24 mediaBlackPointTag
|
||||
case IccTags.MediaWhitePointTag: // 6.4.25 mediaWhitePointTag
|
||||
case IccTags.RedColorantTag: // 6.4.40 redColorantTag
|
||||
return new IccXyzType(data); // Tag Type: XYZType
|
||||
|
||||
case IccTags.BlueTRCTag: // 6.4.5 blueTRCTag
|
||||
case IccTags.GrayTRCTag: // 6.4.19 grayTRCTag
|
||||
case IccTags.GreenTRCTag: // 6.4.21 greenTRCTag
|
||||
case IccTags.RedTRCTag: // 6.4.41 redTRCTag
|
||||
return new IccCurveType(data); // Tag Type: curveType
|
||||
|
||||
case IccTags.CalibrationDateTimeTag: // 6.4.9 calibrationDateTimeTag
|
||||
return new IccDateTimeType(data); // Tag Type: dateTimeType
|
||||
|
||||
case IccTags.CharTargetTag: // 6.4.10 charTargetTag
|
||||
case IccTags.CopyrightTag: // 6.4.13 copyrightTag
|
||||
case IccTags.DeviceMfgDescTag: // 6.4.15 deviceMfgDescTag
|
||||
case IccTags.DeviceModelDescTag: // 6.4.16 deviceModelDescTag
|
||||
case IccTags.ProfileDescriptionTag: // 6.4.32 profileDescriptionTag
|
||||
case IccTags.ScreeningDescTag: // 6.4.42 screeningDescTag
|
||||
case IccTags.ViewingCondDescTag: // 6.4.46 viewingCondDescTag
|
||||
return new IccTextType(data); // Tag Type: textDescriptionType
|
||||
|
||||
case IccTags.ChromaticAdaptationTag: // 6.4.11 chromaticAdaptationTag
|
||||
return new IccS15Fixed16ArrayType(data); // Tag Type: s15Fixed16ArrayType
|
||||
|
||||
case IccTags.ChromaticityTag: // 6.4.12 chromaticityTag
|
||||
return new IccChromaticityType(data); // Tag Type: chromaticityType
|
||||
|
||||
case IccTags.CrdInfoTag: // 6.4.14 crdInfoTag
|
||||
// Tag Type: crdInfoType
|
||||
break;
|
||||
|
||||
case IccTags.DeviceSettingsTag: // 6.4.17 deviceSettingsTag
|
||||
// Tag Type: deviceSettingsType
|
||||
break;
|
||||
|
||||
case IccTags.MeasurementTag: // 6.4.23 measurementTag
|
||||
return new IccMeasurementType(data); // Tag Type: measurementType
|
||||
|
||||
case IccTags.NamedColorTag: // 6.4.26 namedColorTag
|
||||
// Tag Type: namedColorType
|
||||
break;
|
||||
|
||||
case IccTags.NamedColor2Tag: // 6.4.27 namedColor2Tag
|
||||
// Tag Type: namedColor2Type
|
||||
break;
|
||||
|
||||
case IccTags.OutputResponseTag: // 6.4.28 outputResponseTag
|
||||
// Tag Type: responseCurveSet16Type
|
||||
break;
|
||||
|
||||
case IccTags.ProfileSequenceDescTag: // 6.4.33 profileSequenceDescTag
|
||||
// Tag Type: profileSequenceDescType
|
||||
break;
|
||||
|
||||
case IccTags.Ps2CRD0Tag: // 6.4.34 ps2CRD0Tag
|
||||
case IccTags.Ps2CRD1Tag: // 6.4.35 ps2CRD1Tag
|
||||
case IccTags.Ps2CRD2Tag: // 6.4.36 ps2CRD2Tag
|
||||
case IccTags.Ps2CRD3Tag: // 6.4.37 ps2CRD3Tag
|
||||
case IccTags.Ps2CSATag: // 6.4.38 ps2CSATag
|
||||
case IccTags.Ps2RenderingIntentTag: // 6.4.39 ps2RenderingIntentTag
|
||||
// Tag Type: dataType
|
||||
break;
|
||||
|
||||
case IccTags.ScreeningTag: // 6.4.43 screeningTag
|
||||
// Tag Type: screeningType
|
||||
break;
|
||||
|
||||
case IccTags.TechnologyTag: // 6.4.44 technologyTag
|
||||
return new IccSignatureType(data); // Tag Type: signatureType
|
||||
|
||||
case IccTags.UcrbgTag: // 6.4.45 ucrbgTag
|
||||
// Tag Type: ucrbgType
|
||||
break;
|
||||
|
||||
case IccTags.ViewingConditionsTag: // 6.4.47 viewingConditionsTag
|
||||
return new IccViewingConditionsType(data); // Tag Type: viewingConditionsType
|
||||
|
||||
default:
|
||||
return IccUnknownTagType.Parse(data);
|
||||
}
|
||||
|
||||
throw new NotImplementedException($"Tag signature '{tag.Signature}' for ICC v2 profile to implement.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using IccProfileNet.Tags;
|
||||
|
||||
namespace IccProfileNet.Parsers
|
||||
{
|
||||
internal static class IccProfileV4TagParser
|
||||
{
|
||||
/// <summary>
|
||||
/// The profile version number consistent with this ICC specification is “4.4.0.0”.
|
||||
/// <para>TODO - update with correct parsers.</para>
|
||||
/// </summary>
|
||||
public static IccTagTypeBase Parse(byte[] profile, IccTagTableItem tag)
|
||||
{
|
||||
byte[] data = profile.Skip((int)tag.Offset).Take((int)tag.Size).ToArray();
|
||||
switch (tag.Signature)
|
||||
{
|
||||
case IccTags.AToB0Tag: // 9.2.1 AToB0Tag
|
||||
case IccTags.AToB1Tag: // 9.2.2 AToB1Tag
|
||||
case IccTags.AToB2Tag: // 9.2.3 AToB2Tag
|
||||
case IccTags.BToA0Tag: // 9.2.6 BToA0Tag
|
||||
case IccTags.BToA1Tag: // 9.2.7 BToA1Tag
|
||||
case IccTags.BToA2Tag: // 9.2.8 BToA2Tag
|
||||
case IccTags.Preview0Tag: // 9.2.40 preview0Tag
|
||||
case IccTags.Preview1Tag: // 9.2.41 preview1Tag
|
||||
case IccTags.Preview2Tag: // 9.2.42 preview2Tag
|
||||
return ReadlutTypeOrlutABType(data); // Permitted tag types: lut8Type or lut16Type or lutBToAType
|
||||
|
||||
case IccTags.GreenMatrixColumnTag: // 9.2.31 greenMatrixColumnTag
|
||||
case IccTags.LuminanceTag: // 9.2.33 luminanceTag
|
||||
case IccTags.MediaWhitePointTag: // 9.2.36 mediaWhitePointTag
|
||||
case IccTags.BlueMatrixColumnTag: // 9.2.4 blueMatrixColumnTag
|
||||
case IccTags.RedMatrixColumnTag: // 9.2.46 redMatrixColumnTag
|
||||
return new IccXyzType(data); // Permitted tag type: XYZType
|
||||
|
||||
case IccTags.GrayTRCTag: // 9.2.30 grayTRCTag
|
||||
case IccTags.GreenTRCTag: // 9.2.32 greenTRCTag
|
||||
case IccTags.RedTRCTag: // 9.2.47 redTRCTag
|
||||
case IccTags.BlueTRCTag: // 9.2.5 blueTRCTag
|
||||
return IccBaseCurveType.Parse(data); // Permitted tag types: curveType or parametricCurveType
|
||||
|
||||
case IccTags.BToD0Tag: // 9.2.9 BToD0Tag
|
||||
case IccTags.BToD1Tag: // 9.2.10 BToD1Tag
|
||||
case IccTags.BToD2Tag: // 9.2.11 BToD2Tag
|
||||
case IccTags.BToD3Tag: // 9.2.12 BToD3Tag
|
||||
// Allowed tag types: multiProcessElementsType
|
||||
break;
|
||||
|
||||
case IccTags.CalibrationDateTimeTag: // 9.2.13 calibrationDateTimeTag
|
||||
return new IccDateTimeType(data); // Permitted tag type: dateTimeType
|
||||
|
||||
case IccTags.CharTargetTag: // 9.2.14 charTargetTag
|
||||
return new IccTextType(data); // Permitted tag type: textType
|
||||
|
||||
case IccTags.ChromaticAdaptationTag: // 9.2.15 chromaticAdaptationTag
|
||||
return new IccS15Fixed16ArrayType(data); // Permitted tag type: s15Fixed16ArrayType
|
||||
|
||||
case IccTags.ChromaticityTag: // 9.2.16 chromaticityTag
|
||||
// Permitted tag type: chromaticityType
|
||||
break;
|
||||
|
||||
case IccTags.CicpTag: // 9.2.17 cicpTag
|
||||
// Permitted tag type: cicpType
|
||||
break;
|
||||
|
||||
case IccTags.ColorantOrderTag: // 9.2.18 colorantOrderTag
|
||||
// Permitted tag type: colorantOrderType
|
||||
break;
|
||||
|
||||
case IccTags.ColorantTableTag: // 9.2.19 colorantTableTag
|
||||
case IccTags.ColorantTableOutTag: // 9.2.20 colorantTableOutTag
|
||||
// Permitted tag type: colorantTableType
|
||||
break;
|
||||
|
||||
case IccTags.ColorimetricIntentImageStateTag: // 9.2.21 colorimetricIntentImageStateTag
|
||||
case IccTags.PerceptualRenderingIntentGamutTag: // 9.2.39 perceptualRenderingIntentGamutTag
|
||||
case IccTags.SaturationRenderingIntentGamutTag: // 9.2.48 saturationRenderingIntentGamutTag
|
||||
case IccTags.TechnologyTag: // 9.2.49 technologyTag
|
||||
return new IccSignatureType(data); // Permitted tag type: signatureType
|
||||
|
||||
case IccTags.CopyrightTag: // 9.2.22 copyrightTag
|
||||
case IccTags.DeviceMfgDescTag: // 9.2.23 deviceMfgDescTag
|
||||
case IccTags.DeviceModelDescTag: // 9.2.24 deviceModelDescTag
|
||||
case IccTags.ProfileDescriptionTag: // 9.2.43 profileDescriptionTag
|
||||
case IccTags.ViewingCondDescTag: // 9.2.50 viewingCondDescTag
|
||||
return new IccMultiLocalizedUnicodeType(data); // Permitted tag type: multiLocalizedUnicodeType
|
||||
|
||||
case IccTags.DToB0Tag: // 9.2.25 DToB0Tag
|
||||
case IccTags.DToB1Tag: // 9.2.26 DToB1Tag
|
||||
case IccTags.DToB2Tag: // 9.2.27 DToB2Tag
|
||||
case IccTags.DToB3Tag: // 9.2.28 DToB3Tag
|
||||
// Allowed tag types: multiProcessElementsType
|
||||
break;
|
||||
|
||||
case IccTags.GamutTag: // 9.2.29 gamutTag
|
||||
return ReadlutTypeOrlutABType(data); // Permitted tag types: lut8Type or lut16Type or lutBToAType
|
||||
|
||||
case IccTags.MeasurementTag: // 9.2.34 measurementTag
|
||||
return new IccMeasurementType(data);
|
||||
|
||||
case IccTags.MetadataTag: // 9.2.35 metadataTag
|
||||
// Allowed tag types: dictType
|
||||
break;
|
||||
|
||||
case IccTags.NamedColor2Tag: // 9.2.37 namedColor2Tag
|
||||
// Permitted tag type: namedColor2Type
|
||||
break;
|
||||
|
||||
case IccTags.OutputResponseTag: // 9.2.38 outputResponseTag
|
||||
// Permitted tag type: responseCurveSet16Type
|
||||
break;
|
||||
|
||||
case IccTags.ProfileSequenceDescTag: // 9.2.44 profileSequenceDescTag
|
||||
// Permitted tag type: profileSequenceDescType
|
||||
break;
|
||||
|
||||
case IccTags.ProfileSequenceIdentifierTag: // 9.2.45 profileSequenceIdentifierTag
|
||||
// Permitted tag type: profileSequenceIdentifierType
|
||||
break;
|
||||
|
||||
case IccTags.ViewingConditionsTag: // 9.2.51 viewingConditionsTag
|
||||
return new IccViewingConditionsType(data); // Permitted tag type: viewingConditionsType
|
||||
|
||||
default:
|
||||
return IccUnknownTagType.Parse(data);
|
||||
}
|
||||
|
||||
throw new NotImplementedException($"Tag signature '{tag.Signature}' for ICC v4 profile.");
|
||||
}
|
||||
|
||||
private static IccTagTypeBase ReadlutTypeOrlutABType(byte[] bytes)
|
||||
{
|
||||
string typeSignature = IccHelper.GetString(bytes, 0, 4);
|
||||
switch (typeSignature)
|
||||
{
|
||||
case "mft1":
|
||||
case "mft2":
|
||||
return IccBaseLutType.Parse(bytes);
|
||||
|
||||
case "mAB ":
|
||||
case "mBA ":
|
||||
return new IccLutABType(bytes);
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException($"{typeSignature}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
14
src/UglyToad.PdfPig/Graphics/Colors/ICC/Tags/IIccClutType.cs
Normal file
14
src/UglyToad.PdfPig/Graphics/Colors/ICC/Tags/IIccClutType.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace IccProfileNet.Tags
|
||||
{
|
||||
internal interface IIccClutType : IIccProcessor
|
||||
{
|
||||
/// <summary>
|
||||
/// Lookup the values in the color lookup table.
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
double[] LookupClut(double[] input);
|
||||
|
||||
double[] ApplyMatrix(double[] values, IccProfileHeader header);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace IccProfileNet.Tags
|
||||
{
|
||||
internal interface IIccProcessor
|
||||
{
|
||||
double[] Process(double[] input, IccProfileHeader header);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
|
||||
namespace IccProfileNet.Tags
|
||||
{
|
||||
internal abstract class IccBaseCurveType : IccTagTypeBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Curve type signature.
|
||||
/// </summary>
|
||||
public string Signature { get; protected set; }
|
||||
|
||||
protected Lazy<double[]> _parameters;
|
||||
/// <summary>
|
||||
/// TODO
|
||||
/// </summary>
|
||||
public double[] Parameters => _parameters.Value;
|
||||
|
||||
public int BytesRead { get; protected set; }
|
||||
|
||||
public abstract double Process(double values);
|
||||
|
||||
/// <summary>
|
||||
/// TODO
|
||||
/// </summary>
|
||||
public static IccBaseCurveType Parse(byte[] bytes)
|
||||
{
|
||||
string typeSignature = IccHelper.GetString(bytes, TypeSignatureOffset, TypeSignatureLength);
|
||||
switch (typeSignature)
|
||||
{
|
||||
case "curv":
|
||||
return new IccCurveType(bytes);
|
||||
|
||||
case "para":
|
||||
return new IccParametricCurveType(bytes);
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException(typeSignature);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
277
src/UglyToad.PdfPig/Graphics/Colors/ICC/Tags/IccBaseLutType.cs
Normal file
277
src/UglyToad.PdfPig/Graphics/Colors/ICC/Tags/IccBaseLutType.cs
Normal file
@@ -0,0 +1,277 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace IccProfileNet.Tags
|
||||
{
|
||||
internal abstract class IccBaseLutType : IccTagTypeBase, IIccClutType
|
||||
{
|
||||
public const int NumberOfInputChannelsOffset = 8;
|
||||
public const int NumberOfInputChannelsLength = 1;
|
||||
public const int NumberOfOutputChannelsOffset = 9;
|
||||
public const int NumberOfOutputChannelsLength = 1;
|
||||
public const int NumberOfClutPointOffset = 10;
|
||||
public const int NumberOfClutPointLength = 1;
|
||||
public const int E1Offset = 12;
|
||||
public const int E1Length = 4;
|
||||
public const int E2Offset = 16;
|
||||
public const int E2Length = 4;
|
||||
public const int E3Offset = 20;
|
||||
public const int E3Length = 4;
|
||||
public const int E4Offset = 24;
|
||||
public const int E4Length = 4;
|
||||
public const int E5Offset = 28;
|
||||
public const int E5Length = 4;
|
||||
public const int E6Offset = 32;
|
||||
public const int E6Length = 4;
|
||||
public const int E7Offset = 36;
|
||||
public const int E7Length = 4;
|
||||
public const int E8Offset = 40;
|
||||
public const int E8Length = 4;
|
||||
public const int E9Offset = 44;
|
||||
public const int E9Length = 4;
|
||||
|
||||
protected Lazy<int> _numberOfInputChannels;
|
||||
public int NumberOfInputChannels => _numberOfInputChannels.Value;
|
||||
|
||||
protected Lazy<int> _numberOfInputEntries;
|
||||
public int NumberOfInputEntries => _numberOfInputEntries.Value;
|
||||
|
||||
protected Lazy<int> _numberOfOutputChannels;
|
||||
public int NumberOfOutputChannels => _numberOfOutputChannels.Value;
|
||||
|
||||
protected Lazy<int> _numberOfOutputEntries;
|
||||
public int NumberOfOutputEntries => _numberOfOutputEntries.Value;
|
||||
|
||||
protected Lazy<int> _numberOfClutPoints;
|
||||
public int NumberOfClutPoints => _numberOfClutPoints.Value;
|
||||
|
||||
protected Lazy<double> _e1;
|
||||
public double E1 => _e1.Value;
|
||||
|
||||
protected Lazy<double> _e2;
|
||||
public double E2 => _e2.Value;
|
||||
|
||||
protected Lazy<double> _e3;
|
||||
public double E3 => _e3.Value;
|
||||
|
||||
protected Lazy<double> _e4;
|
||||
public double E4 => _e4.Value;
|
||||
|
||||
protected Lazy<double> _e5;
|
||||
public double E5 => _e5.Value;
|
||||
|
||||
protected Lazy<double> _e6;
|
||||
public double E6 => _e6.Value;
|
||||
|
||||
protected Lazy<double> _e7;
|
||||
public double E7 => _e7.Value;
|
||||
|
||||
protected Lazy<double> _e8;
|
||||
public double E8 => _e8.Value;
|
||||
|
||||
protected Lazy<double> _e9;
|
||||
public double E9 => _e9.Value;
|
||||
|
||||
protected Lazy<double[][]> _inputTable;
|
||||
public double[][] InputTable => _inputTable.Value;
|
||||
|
||||
protected Lazy<double[][]> _clut;
|
||||
public double[][] Clut => _clut.Value;
|
||||
|
||||
protected Lazy<double[][]> _outputTable;
|
||||
public double[][] OutputTable => _outputTable.Value;
|
||||
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
#pragma warning disable RCS1160 // Abstract type should not have public constructors.
|
||||
public IccBaseLutType(byte[] rawData)
|
||||
#pragma warning restore RCS1160 // Abstract type should not have public constructors.
|
||||
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
{
|
||||
// Signature check is done in child class
|
||||
|
||||
RawData = rawData;
|
||||
|
||||
_numberOfInputChannels = new Lazy<int>(() =>
|
||||
{
|
||||
// Number of Input Channels (i)
|
||||
// 8
|
||||
return RawData.Skip(NumberOfInputChannelsOffset).Take(NumberOfInputChannelsLength).ToArray()[0];
|
||||
});
|
||||
|
||||
_numberOfOutputChannels = new Lazy<int>(() =>
|
||||
{
|
||||
// Number of Output Channels (o)
|
||||
// 9
|
||||
return RawData.Skip(NumberOfOutputChannelsOffset).Take(NumberOfOutputChannelsLength).ToArray()[0];
|
||||
});
|
||||
|
||||
_numberOfClutPoints = new Lazy<int>(() =>
|
||||
{
|
||||
// Number of CLUT grid points (identical for each side) (g)
|
||||
// 10
|
||||
return RawData.Skip(NumberOfClutPointOffset).Take(NumberOfClutPointLength).ToArray()[0];
|
||||
});
|
||||
|
||||
_e1 = new Lazy<double>(() =>
|
||||
{
|
||||
// Encoded e1 parameter
|
||||
// 12 to 15
|
||||
return IccHelper.Reads15Fixed16Number(RawData.Skip(E1Offset).Take(E1Length).ToArray());
|
||||
});
|
||||
|
||||
_e2 = new Lazy<double>(() =>
|
||||
{
|
||||
// Encoded e2 parameter
|
||||
// 16 to 19
|
||||
return IccHelper.Reads15Fixed16Number(RawData.Skip(E2Offset).Take(E2Length).ToArray());
|
||||
});
|
||||
|
||||
_e3 = new Lazy<double>(() =>
|
||||
{
|
||||
// Encoded e3 parameter
|
||||
// 20 to 23
|
||||
return IccHelper.Reads15Fixed16Number(RawData.Skip(E3Offset).Take(E3Length).ToArray());
|
||||
});
|
||||
|
||||
_e4 = new Lazy<double>(() =>
|
||||
{
|
||||
// Encoded e4 parameter
|
||||
// 24 to 27
|
||||
return IccHelper.Reads15Fixed16Number(RawData.Skip(E4Offset).Take(E4Length).ToArray());
|
||||
});
|
||||
|
||||
_e5 = new Lazy<double>(() =>
|
||||
{
|
||||
// Encoded e5 parameter
|
||||
// 28 to 31
|
||||
return IccHelper.Reads15Fixed16Number(RawData.Skip(E5Offset).Take(E5Length).ToArray());
|
||||
});
|
||||
|
||||
_e6 = new Lazy<double>(() =>
|
||||
{
|
||||
// Encoded e6 parameter
|
||||
// 32 to 35
|
||||
return IccHelper.Reads15Fixed16Number(RawData.Skip(E6Offset).Take(E6Length).ToArray());
|
||||
});
|
||||
|
||||
_e7 = new Lazy<double>(() =>
|
||||
{
|
||||
// Encoded e7 parameter
|
||||
// 36 to 39
|
||||
return IccHelper.Reads15Fixed16Number(RawData.Skip(E7Offset).Take(E7Length).ToArray());
|
||||
});
|
||||
|
||||
_e8 = new Lazy<double>(() =>
|
||||
{
|
||||
// Encoded e8 parameter
|
||||
// 40 to 43
|
||||
return IccHelper.Reads15Fixed16Number(RawData.Skip(E8Offset).Take(E8Length).ToArray());
|
||||
});
|
||||
|
||||
_e9 = new Lazy<double>(() =>
|
||||
{
|
||||
// Encoded e9 parameter
|
||||
// 44 to 47
|
||||
return IccHelper.Reads15Fixed16Number(RawData.Skip(E9Offset).Take(E9Length).ToArray());
|
||||
});
|
||||
}
|
||||
|
||||
public virtual double[] LookupClut(double[] input)
|
||||
{
|
||||
return IccHelper.Lookup(input, Clut, NumberOfClutPoints);
|
||||
}
|
||||
|
||||
public double[] ApplyMatrix(double[] values, IccProfileHeader header)
|
||||
{
|
||||
// p217
|
||||
// In LUT8 and LUT16, the matrix element can only be used when PCS is XYZ.
|
||||
if (header.Pcs == IccProfileConnectionSpace.PCSLAB)
|
||||
{
|
||||
return values;
|
||||
}
|
||||
|
||||
if (values.Length != 3)
|
||||
{
|
||||
throw new InvalidOperationException("Input values dimensions not correct.");
|
||||
}
|
||||
|
||||
double x1 = values[0];
|
||||
double x2 = values[1];
|
||||
double x3 = values[2];
|
||||
|
||||
// Check if identity, then do nothing
|
||||
|
||||
return new double[]
|
||||
{
|
||||
x1 * E1 + x2 * E2 + x3 * E3,
|
||||
x1 * E4 + x2 * E5 + x3 * E6,
|
||||
x1 * E7 + x2 * E8 + x3 * E9
|
||||
};
|
||||
}
|
||||
|
||||
private static double[] ProcessCurves(double[] values, double[][] table)
|
||||
{
|
||||
if (values.Length != table.Length)
|
||||
{
|
||||
throw new InvalidOperationException("TODO");
|
||||
}
|
||||
|
||||
double[] result = new double[values.Length];
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
result[i] = Interpolate(values[i], table[i]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static double Interpolate(double x, double[] values)
|
||||
{
|
||||
// Interpolate
|
||||
double index = (values.Length - 1.0) * x;
|
||||
|
||||
bool hasDecimal = Math.Abs(index % 1) > double.Epsilon * 100;
|
||||
if (!hasDecimal)
|
||||
{
|
||||
return values[(int)index];
|
||||
}
|
||||
|
||||
int indexInt = (int)Math.Floor(index);
|
||||
double w = index - indexInt;
|
||||
double y1 = values[indexInt];
|
||||
double y2 = values[indexInt + 1];
|
||||
return y1 + w * (y2 - y1);
|
||||
}
|
||||
|
||||
public double[] Process(double[] values, IccProfileHeader header)
|
||||
{
|
||||
// p217
|
||||
// LUT8 and LUT16 have the following processing elements: matrix – one-dimensional
|
||||
// curves – CLUT – one-dimensional curves.
|
||||
double[] result = ApplyMatrix(values, header);
|
||||
result = ProcessCurves(result, InputTable);
|
||||
result = LookupClut(result);
|
||||
return ProcessCurves(result, OutputTable);
|
||||
}
|
||||
|
||||
public static IccBaseLutType Parse(byte[] bytes)
|
||||
{
|
||||
string typeSignature = IccHelper.GetString(bytes, IccTagTypeBase.TypeSignatureOffset, IccTagTypeBase.TypeSignatureLength);
|
||||
|
||||
if (typeSignature != "mft1" && typeSignature != "mft2")
|
||||
{
|
||||
throw new ArgumentException(nameof(typeSignature));
|
||||
}
|
||||
|
||||
switch (typeSignature)
|
||||
{
|
||||
case "mft1":
|
||||
return new IccLut8Type(bytes);
|
||||
case "mft2":
|
||||
return new IccLut16Type(bytes);
|
||||
default:
|
||||
throw new ArgumentException(nameof(typeSignature));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace IccProfileNet.Tags
|
||||
{
|
||||
// TODO make lazy
|
||||
internal class IccChromaticityType : IccTagTypeBase
|
||||
{
|
||||
public const int NumberOfDeviceChannelsOffset = 8;
|
||||
public const int NumberOfDeviceChannelsLength = 2;
|
||||
public const int PhosphorOrColorantOffset = 10;
|
||||
public const int PhosphorOrColorantLength = 2;
|
||||
public const int XyCoordinateValuesOfChannel1Offset = 12;
|
||||
public const int XyCoordinateValuesOfChannel1Length = 8;
|
||||
public const int XyCoordinateValuesOfOtherChannelsOffset = 20;
|
||||
|
||||
/// <summary>
|
||||
/// Number of device channels (n).
|
||||
/// </summary>
|
||||
public int NumberOfDeviceChannels { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Phosphor or colorant type.
|
||||
/// </summary>
|
||||
public PhosphorOrColorantType PhosphorOrColorant { get; }
|
||||
|
||||
/// <summary>
|
||||
/// CIE xy coordinate values of channel 1.
|
||||
/// </summary>
|
||||
public double[] XyCoordinateValuesOfChannel1 { get; }
|
||||
|
||||
public double[] XyCoordinateValuesOfOtherChannels { get; }
|
||||
|
||||
public IccChromaticityType(byte[] bytes)
|
||||
{
|
||||
string typeSignature = IccHelper.GetString(bytes, TypeSignatureOffset, TypeSignatureLength);
|
||||
|
||||
if (typeSignature != "chrm")
|
||||
{
|
||||
throw new ArgumentException(nameof(typeSignature));
|
||||
}
|
||||
|
||||
NumberOfDeviceChannels = IccHelper.ReadUInt16(bytes
|
||||
.Skip(NumberOfDeviceChannelsOffset)
|
||||
.Take(NumberOfDeviceChannelsLength)
|
||||
.ToArray());
|
||||
|
||||
/*
|
||||
* Phosphor or colorant Encoded value Channel 1 (x, y) Channel 2 (x, y) Channel 3 (x, y)
|
||||
* type as defined in:
|
||||
* Unknown 0000h Any Any Any
|
||||
* ITU-R BT.709-2 0001h (0,640, 0,330) (0,300, 0,600) (0,150, 0,060)
|
||||
* SMPTE RP145 0002h (0,630, 0,340) (0,310, 0,595) (0,155, 0,070)
|
||||
* EBU Tech. 3213-E 0003h (0,640 0,330) (0,290, 0,600) (0,150, 0,060)
|
||||
* P22 0004h (0,625, 0,340) (0,280, 0,605) (0,155, 0,070)
|
||||
* P3 0005h (0,680, 0,320) (0,265, 0.690) (0,150, 0,060)
|
||||
* ITU-R BT.2020 0006h (0,780, 0,292) (0,170, 0,797) (0,131, 0.046)
|
||||
*/
|
||||
string encodedPhosphorOrColorant = IccHelper.GetHexadecimalString(bytes, PhosphorOrColorantOffset, PhosphorOrColorantLength);
|
||||
switch (encodedPhosphorOrColorant)
|
||||
{
|
||||
case "0000":
|
||||
PhosphorOrColorant = PhosphorOrColorantType.Unknown;
|
||||
break;
|
||||
|
||||
case "0001":
|
||||
PhosphorOrColorant = PhosphorOrColorantType.ITU_R_BT_709_2;
|
||||
break;
|
||||
|
||||
case "0002":
|
||||
PhosphorOrColorant = PhosphorOrColorantType.SMPTE_RP145;
|
||||
break;
|
||||
|
||||
case "0003":
|
||||
PhosphorOrColorant = PhosphorOrColorantType.EBU_Tech_3213_E;
|
||||
break;
|
||||
|
||||
case "0004":
|
||||
PhosphorOrColorant = PhosphorOrColorantType.P22;
|
||||
break;
|
||||
|
||||
case "0005":
|
||||
PhosphorOrColorant = PhosphorOrColorantType.P3;
|
||||
break;
|
||||
|
||||
case "0006":
|
||||
PhosphorOrColorant = PhosphorOrColorantType.ITU_R_BT_2020;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(encodedPhosphorOrColorant));
|
||||
}
|
||||
|
||||
var channel1Bytes = bytes
|
||||
.Skip(XyCoordinateValuesOfChannel1Offset)
|
||||
.Take(XyCoordinateValuesOfChannel1Length)
|
||||
.ToArray();
|
||||
|
||||
// TODO - array of type u16Fixed16Number[2]
|
||||
|
||||
var otherChannelsBytes = bytes
|
||||
.Skip(XyCoordinateValuesOfOtherChannelsOffset)
|
||||
.ToArray();
|
||||
|
||||
// TODO - array of type u16Fixed16Number[...]
|
||||
|
||||
RawData = bytes;
|
||||
|
||||
throw new NotImplementedException("IccChromaticityType implementation not finished");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Phosphor or colorant type.
|
||||
/// </summary>
|
||||
public enum PhosphorOrColorantType : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Unknown.
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <term>Channel 1 (x, y)</term>
|
||||
/// Any
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>Channel 2 (x, y)</term>
|
||||
/// Any
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>Channel 3 (x, y)</term>
|
||||
/// Any
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
Unknown = 0,
|
||||
|
||||
/// <summary>
|
||||
/// ITU-R BT.709-2.
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <term>Channel 1 (x, y)</term>
|
||||
/// (0,640, 0,330)
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>Channel 2 (x, y)</term>
|
||||
/// (0,300, 0,600)
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>Channel 3 (x, y)</term>
|
||||
/// (0,150, 0,060)
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
ITU_R_BT_709_2 = 1,
|
||||
|
||||
/// <summary>
|
||||
/// SMPTE RP145.
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <term>Channel 1 (x, y)</term>
|
||||
/// (0,630, 0,340)
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>Channel 2 (x, y)</term>
|
||||
/// (0,310, 0,595)
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>Channel 3 (x, y)</term>
|
||||
/// (0,155, 0,070)
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
SMPTE_RP145 = 2,
|
||||
|
||||
/// <summary>
|
||||
/// EBU Tech. 3213-E.
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <term>Channel 1 (x, y)</term>
|
||||
/// (0,640 0,330)
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>Channel 2 (x, y)</term>
|
||||
/// (0,290, 0,600)
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>Channel 3 (x, y)</term>
|
||||
/// (0,150, 0,060)
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
EBU_Tech_3213_E = 3,
|
||||
|
||||
/// <summary>
|
||||
/// P22.
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <term>Channel 1 (x, y)</term>
|
||||
/// (0,625, 0,340)
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>Channel 2 (x, y)</term>
|
||||
/// (0,280, 0,605)
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>Channel 3 (x, y)</term>
|
||||
/// (0,155, 0,070)
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
P22 = 4,
|
||||
|
||||
/// <summary>
|
||||
/// P3.
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <term>Channel 1 (x, y)</term>
|
||||
/// (0,680, 0,320)
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>Channel 2 (x, y)</term>
|
||||
/// (0,265, 0.690)
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>Channel 3 (x, y)</term>
|
||||
/// (0,150, 0,060)
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
P3 = 5,
|
||||
|
||||
/// <summary>
|
||||
/// ITU-R BT.2020.
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <term>Channel 1 (x, y)</term>
|
||||
/// (0,780, 0,292)
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>Channel 2 (x, y)</term>
|
||||
/// (0,170, 0,797)
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>Channel 3 (x, y)</term>
|
||||
/// (0,131, 0.046)
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
ITU_R_BT_2020 = 6
|
||||
}
|
||||
}
|
||||
}
|
||||
157
src/UglyToad.PdfPig/Graphics/Colors/ICC/Tags/IccCurveType.cs
Normal file
157
src/UglyToad.PdfPig/Graphics/Colors/ICC/Tags/IccCurveType.cs
Normal file
@@ -0,0 +1,157 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace IccProfileNet.Tags
|
||||
{
|
||||
internal sealed class IccCurveType : IccBaseCurveType
|
||||
{
|
||||
public const int CountOffset = 8;
|
||||
public const int CountLength = 4;
|
||||
|
||||
private readonly uint _parametersCount;
|
||||
|
||||
public CurveTypeType CurveType { get; }
|
||||
|
||||
private readonly Func<double, double> _func;
|
||||
private readonly Lazy<double> _gamma;
|
||||
|
||||
public IccCurveType(byte[] rawData)
|
||||
{
|
||||
string typeSignature = IccHelper.GetString(rawData, TypeSignatureOffset, TypeSignatureLength);
|
||||
|
||||
if (typeSignature != "curv")
|
||||
{
|
||||
throw new ArgumentException(nameof(typeSignature));
|
||||
}
|
||||
|
||||
RawData = rawData;
|
||||
Signature = "curv";
|
||||
|
||||
// Count value specifying the number of entries (n) that follow
|
||||
// 8 to 11
|
||||
_parametersCount = IccHelper.ReadUInt32(RawData.Skip(CountOffset).Take(CountLength).ToArray());
|
||||
BytesRead = CountOffset + CountLength + (2 * (int)_parametersCount);
|
||||
|
||||
_parameters = new Lazy<double[]>(() =>
|
||||
{
|
||||
double[] values = new double[_parametersCount];
|
||||
|
||||
// Actual curve values starting with the zeroth entry and ending with the entry n 1
|
||||
// 12 to end
|
||||
// The curveType embodies a one-dimensional function which maps an input value in the domain of the function
|
||||
// to an output value in the range of the function.The domain and range values are in the range of 0,0 to 1,0.
|
||||
// - When n is equal to 0, an identity response is assumed.
|
||||
// - When n is equal to 1, then the curve value shall be interpreted as a gamma value, encoded as
|
||||
// u8Fixed8Number. Gamma shall be interpreted as the exponent in the equation y = x^g and not as an inverse.
|
||||
// - When n is greater than 1, the curve values(which embody a sampled one - dimensional function) shall be
|
||||
// defined as follows:
|
||||
// - The first entry represents the input value 0,0, the last entry represents the input value 1,0, and intermediate
|
||||
// entries are uniformly spaced using an increment of 1,0 / (n-1). These entries are encoded as uInt16Numbers
|
||||
// (i.e. the values represented by the entries, which are in the range 0,0 to 1,0 are encoded in the range 0 to
|
||||
// 65 535). Function values between the entries shall be obtained through linear interpolation.
|
||||
if (_parametersCount == 0)
|
||||
{
|
||||
// When n is equal to 0, an identity response is assumed.
|
||||
}
|
||||
else if (_parametersCount == 1)
|
||||
{
|
||||
// When n is equal to 1, then the curve value shall be interpreted as a gamma value, encoded as
|
||||
// u8Fixed8Number. Gamma shall be interpreted as the exponent in the equation y = x^g and not as an inverse.
|
||||
// * If n = 1, the field length is 2 bytes and the value is encoded as a u8Fixed8Number
|
||||
values[0] = IccHelper.ReadU8Fixed8Number(RawData
|
||||
.Skip(CountOffset + CountLength)
|
||||
.Take(IccHelper.UInt16Length).ToArray());
|
||||
}
|
||||
else
|
||||
{
|
||||
// When n is greater than 1, the curve values(which embody a sampled one - dimensional function) shall be
|
||||
// defined as follows:
|
||||
// The first entry represents the input value 0,0, the last entry represents the input value 1,0, and intermediate
|
||||
// entries are uniformly spaced using an increment of 1,0 / (n-1). These entries are encoded as uInt16Numbers
|
||||
// (i.e. the values represented by the entries, which are in the range 0,0 to 1,0 are encoded in the range 0 to
|
||||
// 65 535). Function values between the entries shall be obtained through linear interpolation.
|
||||
for (int c = 0; c < _parametersCount; c++)
|
||||
{
|
||||
values[c] = IccHelper.ReadUInt16(RawData
|
||||
.Skip(CountOffset + CountLength + (IccHelper.UInt16Length * c))
|
||||
.Take(IccHelper.UInt16Length)
|
||||
.ToArray()) / (double)ushort.MaxValue;
|
||||
}
|
||||
}
|
||||
return values;
|
||||
});
|
||||
|
||||
_gamma = new Lazy<double>(() =>
|
||||
{
|
||||
if (_parametersCount != 1)
|
||||
{
|
||||
throw new InvalidOperationException("TODO");
|
||||
}
|
||||
return Parameters[0];
|
||||
});
|
||||
|
||||
switch (_parametersCount)
|
||||
{
|
||||
case 0:
|
||||
CurveType = CurveTypeType.Identity;
|
||||
_func = x => x;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
CurveType = CurveTypeType.Gamma;
|
||||
_func = x => Math.Pow(x, _gamma.Value);
|
||||
break;
|
||||
|
||||
default:
|
||||
CurveType = CurveTypeType.LinearInterpolation;
|
||||
_func = x =>
|
||||
{
|
||||
// Interpolate
|
||||
double index = (Parameters.Length - 1.0) * x;
|
||||
|
||||
bool hasDecimal = Math.Abs(index % 1) > double.Epsilon * 100;
|
||||
if (!hasDecimal)
|
||||
{
|
||||
return Parameters[(int)index];
|
||||
}
|
||||
|
||||
int indexInt = (int)Math.Floor(index);
|
||||
double w = index - indexInt;
|
||||
double y1 = Parameters[indexInt];
|
||||
double y2 = Parameters[indexInt + 1];
|
||||
return y1 + w * (y2 - y1);
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override double Process(double values)
|
||||
{
|
||||
return _func(values);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
switch (CurveType)
|
||||
{
|
||||
case CurveTypeType.Identity:
|
||||
return CurveType.ToString();
|
||||
|
||||
default:
|
||||
if (Parameters.Length > 1)
|
||||
{
|
||||
return $"{CurveType} ({Parameters.Length} points)";
|
||||
}
|
||||
|
||||
return $"{CurveType} ({Math.Round(Parameters[0], 4)})";
|
||||
}
|
||||
}
|
||||
|
||||
public enum CurveTypeType : byte
|
||||
{
|
||||
Identity = 0,
|
||||
Gamma = 1,
|
||||
LinearInterpolation = 2
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace IccProfileNet.Tags
|
||||
{
|
||||
internal sealed class IccDateTimeType : IccTagTypeBase
|
||||
{
|
||||
public const int DateAndTimeOffset = 8;
|
||||
public const int DateAndTimeLength = 12;
|
||||
|
||||
private readonly Lazy<DateTime> _dateTime;
|
||||
/// <summary>
|
||||
/// Value.
|
||||
/// </summary>
|
||||
public DateTime DateTime => _dateTime.Value;
|
||||
|
||||
public IccDateTimeType(byte[] data)
|
||||
{
|
||||
string typeSignature = IccHelper.GetString(data, TypeSignatureOffset, TypeSignatureLength);
|
||||
|
||||
if (typeSignature != "dtim")
|
||||
{
|
||||
throw new ArgumentException(nameof(typeSignature));
|
||||
}
|
||||
|
||||
RawData = data;
|
||||
_dateTime = new Lazy<DateTime>(() =>
|
||||
{
|
||||
var dt = IccHelper.ReadDateTime(RawData.Skip(DateAndTimeOffset).Take(DateAndTimeLength).ToArray());
|
||||
if (!dt.HasValue)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(RawData), "Could not get date value.");
|
||||
}
|
||||
|
||||
return dt.Value;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
111
src/UglyToad.PdfPig/Graphics/Colors/ICC/Tags/IccLut16Type.cs
Normal file
111
src/UglyToad.PdfPig/Graphics/Colors/ICC/Tags/IccLut16Type.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace IccProfileNet.Tags
|
||||
{
|
||||
internal sealed class IccLut16Type : IccBaseLutType
|
||||
{
|
||||
public const int NumberOfInputEntriesOffset = 48;
|
||||
public const int NumberOfInputEntriesLength = 2;
|
||||
public const int NumberOfOutputEntriesOffset = 50;
|
||||
public const int NumberOfOutputEntriesLength = 2;
|
||||
public const int InputTablesOffset = 52;
|
||||
|
||||
public IccLut16Type(byte[] rawData) : base(rawData)
|
||||
{
|
||||
string typeSignature = IccHelper.GetString(rawData, TypeSignatureOffset, TypeSignatureLength);
|
||||
|
||||
if (typeSignature != "mft2")
|
||||
{
|
||||
throw new ArgumentException(nameof(typeSignature));
|
||||
}
|
||||
|
||||
_numberOfInputEntries = new Lazy<int>(() =>
|
||||
{
|
||||
// Number of input table entries (n)
|
||||
// 48 to 49
|
||||
return IccHelper.ReadUInt16(RawData
|
||||
.Skip(NumberOfInputEntriesOffset)
|
||||
.Take(NumberOfInputEntriesLength)
|
||||
.ToArray());
|
||||
});
|
||||
|
||||
_numberOfOutputEntries = new Lazy<int>(() =>
|
||||
{
|
||||
// Number of output table entries (m)
|
||||
// 50 to 51
|
||||
return IccHelper.ReadUInt16(RawData
|
||||
.Skip(NumberOfOutputEntriesOffset)
|
||||
.Take(NumberOfOutputEntriesLength)
|
||||
.ToArray());
|
||||
});
|
||||
|
||||
_inputTable = new Lazy<double[][]>(() =>
|
||||
{
|
||||
// Input tables
|
||||
// 52 to 51+(2ni)
|
||||
int l = 0;
|
||||
int inputTableBytesL = IccHelper.UInt16Length * NumberOfInputEntries * NumberOfInputChannels;
|
||||
double[][] inputTable = new double[NumberOfInputChannels][];
|
||||
var tableBytes = RawData.Skip(InputTablesOffset).Take(inputTableBytesL).ToArray();
|
||||
for (int i = 0; i < inputTable.Length; i++)
|
||||
{
|
||||
double[] domain = new double[NumberOfInputEntries];
|
||||
for (int j = 0; j < domain.Length; j++)
|
||||
{
|
||||
domain[j] = IccHelper.ReadUInt16(tableBytes.Skip(l).Take(IccHelper.UInt16Length).ToArray()) / (double)ushort.MaxValue;
|
||||
l += IccHelper.UInt16Length;
|
||||
}
|
||||
inputTable[i] = domain;
|
||||
}
|
||||
return inputTable;
|
||||
});
|
||||
|
||||
_clut = new Lazy<double[][]>(() =>
|
||||
{
|
||||
// CLUT values
|
||||
int l = 0;
|
||||
int clutValuesBytesL = IccHelper.UInt16Length * (int)Math.Pow(NumberOfClutPoints, NumberOfInputChannels) * NumberOfOutputChannels;
|
||||
int inputTableBytesL = IccHelper.UInt16Length * NumberOfInputEntries * NumberOfInputChannels;
|
||||
var tableBytes = RawData.Skip(InputTablesOffset + inputTableBytesL).Take(clutValuesBytesL).ToArray();
|
||||
|
||||
int clutSize = (int)Math.Pow(NumberOfClutPoints, NumberOfInputChannels);
|
||||
double[][] clut = new double[clutSize][];
|
||||
for (int i = 0; i < clut.Length; i++)
|
||||
{
|
||||
double[] oArray = new double[NumberOfOutputChannels];
|
||||
for (int o = 0; o < oArray.Length; o++)
|
||||
{
|
||||
oArray[o] = IccHelper.ReadUInt16(tableBytes.Skip(l).Take(IccHelper.UInt16Length).ToArray()) / (double)ushort.MaxValue;
|
||||
l += IccHelper.UInt16Length;
|
||||
}
|
||||
|
||||
clut[i] = oArray;
|
||||
}
|
||||
return clut;
|
||||
});
|
||||
|
||||
_outputTable = new Lazy<double[][]>(() =>
|
||||
{
|
||||
// Output tables
|
||||
int l = 0;
|
||||
//int outputBytesL = IccHelper.UInt16Length * outputTableEntries * output;
|
||||
double[][] outputTable = new double[NumberOfOutputChannels][];
|
||||
int clutValuesBytesL = IccHelper.UInt16Length * (int)Math.Pow(NumberOfClutPoints, NumberOfInputChannels) * NumberOfOutputChannels;
|
||||
int inputTableBytesL = IccHelper.UInt16Length * NumberOfInputEntries * NumberOfInputChannels;
|
||||
var tableBytes = RawData.Skip(InputTablesOffset + inputTableBytesL + clutValuesBytesL).ToArray();
|
||||
for (int i = 0; i < outputTable.Length; i++)
|
||||
{
|
||||
double[] domain = new double[NumberOfOutputEntries];
|
||||
for (int j = 0; j < domain.Length; j++)
|
||||
{
|
||||
domain[j] = IccHelper.ReadUInt16(tableBytes.Skip(l).Take(IccHelper.UInt16Length).ToArray()) / (double)ushort.MaxValue;
|
||||
l += IccHelper.UInt16Length;
|
||||
}
|
||||
outputTable[i] = domain;
|
||||
}
|
||||
return outputTable;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
87
src/UglyToad.PdfPig/Graphics/Colors/ICC/Tags/IccLut8Type.cs
Normal file
87
src/UglyToad.PdfPig/Graphics/Colors/ICC/Tags/IccLut8Type.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace IccProfileNet.Tags
|
||||
{
|
||||
/// <summary>
|
||||
/// TODO
|
||||
/// </summary>
|
||||
internal sealed class IccLut8Type : IccBaseLutType
|
||||
{
|
||||
/// <summary>
|
||||
/// TODO
|
||||
/// </summary>
|
||||
public const int InputTablesOffset = 48;
|
||||
|
||||
/// <summary>
|
||||
/// TODO
|
||||
/// </summary>
|
||||
public IccLut8Type(byte[] rawData) : base(rawData)
|
||||
{
|
||||
string typeSignature = IccHelper.GetString(rawData, TypeSignatureOffset, TypeSignatureLength);
|
||||
|
||||
if (typeSignature != "mft1")
|
||||
{
|
||||
throw new ArgumentException(nameof(typeSignature));
|
||||
}
|
||||
|
||||
RawData = rawData;
|
||||
|
||||
_numberOfInputEntries = new Lazy<int>(() => 256);
|
||||
_numberOfOutputEntries = new Lazy<int>(() => 256);
|
||||
|
||||
_inputTable = new Lazy<double[][]>(() =>
|
||||
{
|
||||
// Input tables
|
||||
int inputTableBytesL = NumberOfInputEntries * NumberOfInputChannels;
|
||||
double[][] inputTable = new double[NumberOfInputChannels][];
|
||||
var tableBytes = RawData.Skip(InputTablesOffset).Take(inputTableBytesL).ToArray();
|
||||
for (int i = 0; i < inputTable.Length; i++)
|
||||
{
|
||||
inputTable[i] = tableBytes.Skip(i * NumberOfInputEntries).Take(NumberOfInputEntries).Select(v => v / (double)byte.MaxValue).ToArray();
|
||||
}
|
||||
return inputTable;
|
||||
});
|
||||
|
||||
_clut = new Lazy<double[][]>(() =>
|
||||
{
|
||||
// CLUT values
|
||||
int l = 0;
|
||||
int clutValuesBytesL = (int)Math.Pow(NumberOfClutPoints, NumberOfInputChannels) * NumberOfOutputChannels;
|
||||
int inputTableBytesL = NumberOfInputEntries * NumberOfInputChannels;
|
||||
var tableBytes = RawData.Skip(InputTablesOffset + inputTableBytesL).Take(clutValuesBytesL).ToArray();
|
||||
|
||||
int clutSize = (int)Math.Pow(NumberOfClutPoints, NumberOfInputChannels);
|
||||
double[][] clut = new double[clutSize][];
|
||||
for (int i = 0; i < clut.Length; i++)
|
||||
{
|
||||
double[] oArray = new double[NumberOfOutputChannels];
|
||||
for (int o = 0; o < oArray.Length; o++)
|
||||
{
|
||||
oArray[o] = tableBytes.Skip(l).Take(1).Single() / (double)byte.MaxValue;
|
||||
l++;
|
||||
}
|
||||
|
||||
clut[i] = oArray;
|
||||
}
|
||||
|
||||
return clut;
|
||||
});
|
||||
|
||||
_outputTable = new Lazy<double[][]>(() =>
|
||||
{
|
||||
// Output tables
|
||||
int outputTableByteL = NumberOfOutputEntries * NumberOfOutputChannels;
|
||||
int clutValuesBytesL = (int)Math.Pow(NumberOfClutPoints, NumberOfInputChannels) * NumberOfOutputChannels;
|
||||
int inputTableBytesL = NumberOfInputEntries * NumberOfInputChannels;
|
||||
double[][] outputTable = new double[NumberOfOutputChannels][];
|
||||
var tableBytes = RawData.Skip(InputTablesOffset + inputTableBytesL + clutValuesBytesL).ToArray();
|
||||
for (int i = 0; i < outputTable.Length; i++)
|
||||
{
|
||||
outputTable[i] = tableBytes.Skip(i * NumberOfOutputEntries).Take(NumberOfOutputEntries).Select(v => v / (double)byte.MaxValue).ToArray();
|
||||
}
|
||||
return outputTable;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
614
src/UglyToad.PdfPig/Graphics/Colors/ICC/Tags/IccLutABType.cs
Normal file
614
src/UglyToad.PdfPig/Graphics/Colors/ICC/Tags/IccLutABType.cs
Normal file
@@ -0,0 +1,614 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace IccProfileNet.Tags
|
||||
{
|
||||
/*
|
||||
* It is possible to use any or all of these processing elements. At least one processing element shall be included.
|
||||
* Only the following combinations are permitted:
|
||||
* - B;
|
||||
* - M, Matrix, B;
|
||||
* - A, CLUT, B;
|
||||
* - A, CLUT, M, Matrix, B.
|
||||
* Other combinations can be achieved by setting processing element values to identity transforms.
|
||||
*/
|
||||
internal sealed class IccLutABType : IccTagTypeBase, IIccClutType
|
||||
{
|
||||
public const int NumberOfInputChannelsOffset = 8;
|
||||
public const int NumberOfInputChannelsLength = 1;
|
||||
public const int NumberOfOutputChannelsOffset = 9;
|
||||
public const int NumberOfOutputChannelsLength = 1;
|
||||
public const int OffsetFirstBCurveOffset = 12;
|
||||
public const int OffsetFirstBCurveLength = 4;
|
||||
public const int OffsetMatrixOffset = 16;
|
||||
public const int OffsetMatrixLength = 4;
|
||||
public const int OffsetFirstMCurveOffset = 20;
|
||||
public const int OffsetFirstMCurveLength = 4;
|
||||
public const int OffsetClutOffset = 24;
|
||||
public const int OffsetClutLength = 4;
|
||||
public const int OffsetFirstACurveOffset = 28;
|
||||
public const int OffsetFirstACurveLength = 4;
|
||||
public const int NumberOfClutGridPointsOffset = 0;
|
||||
public const int NumberOfClutGridPointsLength = 16;
|
||||
public const int ClutPrecisionOffset = 16;
|
||||
public const int ClutPrecisionLength = 1;
|
||||
public const int ClutDataPointsOffset = 20;
|
||||
|
||||
/// <summary>
|
||||
/// A to B or B to A
|
||||
/// </summary>
|
||||
public LutABType Type { get; }
|
||||
|
||||
private readonly Lazy<byte> _numberOfInputChannels;
|
||||
/// <summary>
|
||||
/// Number of Input Channels (i).
|
||||
/// </summary>
|
||||
public int NumberOfInputChannels => _numberOfInputChannels.Value;
|
||||
|
||||
private readonly Lazy<byte> _numberOfOutputChannels;
|
||||
/// <summary>
|
||||
/// Number of Output Channels (o).
|
||||
/// </summary>
|
||||
public int NumberOfOutputChannels => _numberOfOutputChannels.Value;
|
||||
|
||||
private readonly Lazy<int> _offsetFirstBCurve;
|
||||
/// <summary>
|
||||
/// Offset to first “B” curve.
|
||||
/// </summary>
|
||||
public int OffsetFirstBCurve => _offsetFirstBCurve.Value;
|
||||
|
||||
private readonly Lazy<IccBaseCurveType[]> _bCurves;
|
||||
public IccBaseCurveType[] BCurves => _bCurves.Value;
|
||||
|
||||
private readonly Lazy<int> _offsetMatrix;
|
||||
public int OffsetMatrix => _offsetMatrix.Value;
|
||||
|
||||
private readonly Lazy<double[]> _matrix;
|
||||
|
||||
/// <summary>
|
||||
/// [e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12]
|
||||
/// </summary>
|
||||
public double[] Matrix => _matrix.Value;
|
||||
|
||||
/// <summary>
|
||||
/// <c>true</c> if Matrix is defined.
|
||||
/// </summary>
|
||||
public bool HasMatrix => OffsetMatrix != 0;
|
||||
|
||||
private readonly Lazy<int> _offsetFirstMCurve;
|
||||
/// <summary>
|
||||
/// Offset to first “M” curve.
|
||||
/// </summary>
|
||||
public int OffsetFirstMCurve => _offsetFirstMCurve.Value;
|
||||
|
||||
private readonly Lazy<IccBaseCurveType[]> _mCurves;
|
||||
public IccBaseCurveType[] MCurves => _mCurves.Value;
|
||||
|
||||
/// <summary>
|
||||
/// <c>true</c> if M curves are defined.
|
||||
/// </summary>
|
||||
public bool HasMCurves => OffsetFirstMCurve != 0;
|
||||
|
||||
private readonly Lazy<byte[]> _clutGridPoints;
|
||||
public byte[] ClutGridPoints => _clutGridPoints.Value;
|
||||
|
||||
private readonly Lazy<int> _offsetClut;
|
||||
public int OffsetClut => _offsetClut.Value;
|
||||
|
||||
private readonly Lazy<double[][]> _clut;
|
||||
|
||||
/// <summary>
|
||||
/// Multi-dimensional colour lookup table.
|
||||
/// </summary>
|
||||
public double[][] Clut => _clut.Value;
|
||||
|
||||
/// <summary>
|
||||
/// <c>true</c> if CLUT is defined.
|
||||
/// </summary>
|
||||
public bool HasClut => OffsetClut != 0;
|
||||
|
||||
private readonly Lazy<int> _offsetFirstACurve;
|
||||
/// <summary>
|
||||
/// Offset to first “A” curve.
|
||||
/// </summary>
|
||||
public int OffsetFirstACurve => _offsetFirstACurve.Value;
|
||||
|
||||
private readonly Lazy<IccBaseCurveType[]> _aCurves;
|
||||
public IccBaseCurveType[] ACurves => _aCurves.Value;
|
||||
|
||||
/// <summary>
|
||||
/// <c>true</c> if A curves are defined.
|
||||
/// </summary>
|
||||
public bool HasACurve => OffsetFirstACurve != 0;
|
||||
|
||||
private readonly Lazy<double[]> _lookupWeights;
|
||||
|
||||
public IccLutABType(byte[] data)
|
||||
{
|
||||
string typeSignature = IccHelper.GetString(data, TypeSignatureOffset, TypeSignatureLength);
|
||||
|
||||
if (typeSignature != "mAB " && typeSignature != "mBA ")
|
||||
{
|
||||
throw new ArgumentException(nameof(typeSignature));
|
||||
}
|
||||
|
||||
Type = typeSignature == "mAB " ? LutABType.AB : LutABType.BA;
|
||||
RawData = data;
|
||||
|
||||
_numberOfInputChannels = new Lazy<byte>(() =>
|
||||
{
|
||||
return RawData
|
||||
.Skip(NumberOfInputChannelsOffset)
|
||||
.Take(NumberOfInputChannelsLength)
|
||||
.Single();
|
||||
});
|
||||
|
||||
_numberOfOutputChannels = new Lazy<byte>(() =>
|
||||
{
|
||||
return RawData
|
||||
.Skip(NumberOfOutputChannelsOffset)
|
||||
.Take(NumberOfOutputChannelsLength)
|
||||
.Single();
|
||||
});
|
||||
|
||||
_offsetFirstBCurve = new Lazy<int>(() =>
|
||||
{
|
||||
// Offset to first “B” curve
|
||||
// 12 to 15
|
||||
return (int)IccHelper.ReadUInt32(RawData
|
||||
.Skip(OffsetFirstBCurveOffset)
|
||||
.Take(OffsetFirstBCurveLength)
|
||||
.ToArray());
|
||||
});
|
||||
|
||||
_offsetMatrix = new Lazy<int>(() =>
|
||||
{
|
||||
// Offset to matrix
|
||||
// 16 to 19
|
||||
return (int)IccHelper.ReadUInt32(RawData
|
||||
.Skip(OffsetMatrixOffset)
|
||||
.Take(OffsetMatrixLength)
|
||||
.ToArray());
|
||||
});
|
||||
|
||||
_offsetFirstMCurve = new Lazy<int>(() =>
|
||||
{
|
||||
// Offset to first “M” curve
|
||||
// 20 to 23
|
||||
return (int)IccHelper.ReadUInt32(RawData
|
||||
.Skip(OffsetFirstMCurveOffset)
|
||||
.Take(OffsetFirstMCurveLength)
|
||||
.ToArray());
|
||||
});
|
||||
|
||||
_offsetClut = new Lazy<int>(() =>
|
||||
{
|
||||
// Offset to CLUT
|
||||
// 24 to 27
|
||||
return (int)IccHelper.ReadUInt32(RawData
|
||||
.Skip(OffsetClutOffset)
|
||||
.Take(OffsetClutLength)
|
||||
.ToArray());
|
||||
});
|
||||
|
||||
_offsetFirstACurve = new Lazy<int>(() =>
|
||||
{
|
||||
// Offset to first “A” curve
|
||||
// 28 to 31
|
||||
return (int)IccHelper.ReadUInt32(RawData
|
||||
.Skip(OffsetFirstACurveOffset)
|
||||
.Take(OffsetFirstACurveLength)
|
||||
.ToArray());
|
||||
});
|
||||
|
||||
_aCurves = new Lazy<IccBaseCurveType[]>(() =>
|
||||
{
|
||||
if (OffsetFirstACurve == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
int offset = 0;
|
||||
IccBaseCurveType[] aCurves = new IccBaseCurveType[Type == LutABType.AB ? NumberOfInputChannels : NumberOfOutputChannels];
|
||||
for (byte a = 0; a < aCurves.Length; a++)
|
||||
{
|
||||
var curve = IccBaseCurveType.Parse(RawData.Skip((int)OffsetFirstACurve + offset).ToArray());
|
||||
aCurves[a] = curve;
|
||||
offset += IccHelper.AdjustOffsetTo4ByteBoundary(curve.BytesRead);
|
||||
}
|
||||
return aCurves;
|
||||
});
|
||||
|
||||
_bCurves = new Lazy<IccBaseCurveType[]>(() =>
|
||||
{
|
||||
int offset = 0;
|
||||
IccBaseCurveType[] bCurves = new IccBaseCurveType[Type == LutABType.AB ? NumberOfOutputChannels : NumberOfInputChannels];
|
||||
for (byte b = 0; b < bCurves.Length; b++)
|
||||
{
|
||||
var curve = IccBaseCurveType.Parse(RawData.Skip((int)OffsetFirstBCurve + offset).ToArray());
|
||||
bCurves[b] = curve;
|
||||
offset += IccHelper.AdjustOffsetTo4ByteBoundary(curve.BytesRead);
|
||||
}
|
||||
return bCurves;
|
||||
});
|
||||
|
||||
_mCurves = new Lazy<IccBaseCurveType[]>(() =>
|
||||
{
|
||||
if (OffsetFirstMCurve == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
int offset = 0;
|
||||
IccBaseCurveType[] mCurves = new IccBaseCurveType[Type == LutABType.AB ? NumberOfOutputChannels : NumberOfInputChannels];
|
||||
for (byte m = 0; m < mCurves.Length; m++)
|
||||
{
|
||||
var curve = IccBaseCurveType.Parse(RawData.Skip((int)OffsetFirstMCurve + offset).ToArray());
|
||||
mCurves[m] = curve;
|
||||
offset += IccHelper.AdjustOffsetTo4ByteBoundary(curve.BytesRead);
|
||||
}
|
||||
return mCurves;
|
||||
});
|
||||
|
||||
_matrix = new Lazy<double[]>(() =>
|
||||
{
|
||||
if (OffsetMatrix == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
double[] matrix = new double[3 * 4];
|
||||
byte[] matrixData = RawData.Skip((int)OffsetMatrix).Take(matrix.Length * IccHelper.S15Fixed16Length).ToArray();
|
||||
return IccHelper.Reads15Fixed16Array(matrixData);
|
||||
});
|
||||
|
||||
_clutGridPoints = new Lazy<byte[]>(() =>
|
||||
{
|
||||
if (OffsetClut == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return RawData
|
||||
.Skip(OffsetClut + NumberOfClutGridPointsOffset)
|
||||
.Take(NumberOfInputChannels).ToArray();
|
||||
});
|
||||
|
||||
_clut = new Lazy<double[][]>(() =>
|
||||
{
|
||||
// CLUT
|
||||
if (OffsetClut == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (NumberOfInputChannels > NumberOfClutGridPointsLength)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(NumberOfInputChannels));
|
||||
}
|
||||
|
||||
byte precision = RawData
|
||||
.Skip(OffsetClut + ClutPrecisionOffset)
|
||||
.Take(ClutPrecisionLength)
|
||||
.Single();
|
||||
|
||||
byte[] clutData = RawData.Skip(OffsetClut + ClutDataPointsOffset).ToArray();
|
||||
|
||||
Func<byte[], double> reader;
|
||||
if (precision == 1)
|
||||
{
|
||||
reader = new Func<byte[], double>(array => IccHelper.ReadUInt8(array) / (double)byte.MaxValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
reader = new Func<byte[], double>(array => IccHelper.ReadUInt16(array) / (double)ushort.MaxValue);
|
||||
}
|
||||
|
||||
int l = 0;
|
||||
int clutSize = 1;
|
||||
foreach (byte gridPoint in ClutGridPoints)
|
||||
{
|
||||
clutSize *= gridPoint;
|
||||
}
|
||||
|
||||
double[][] clut = new double[clutSize][];
|
||||
for (int i = 0; i < clut.Length; i++)
|
||||
{
|
||||
double[] oArray = new double[NumberOfOutputChannels];
|
||||
for (int o = 0; o < oArray.Length; o++)
|
||||
{
|
||||
oArray[o] = reader(clutData.Skip(l).Take(precision).ToArray());
|
||||
l += precision;
|
||||
}
|
||||
|
||||
clut[i] = oArray;
|
||||
}
|
||||
return clut;
|
||||
});
|
||||
|
||||
_lookupWeights = new Lazy<double[]>(() =>
|
||||
{
|
||||
double[] weights = null;
|
||||
if (ClutGridPoints != null)
|
||||
{
|
||||
// Pre compute Lookup weigth
|
||||
// Can be optimised by setting _lookupWeigths = ClutGridPoints;
|
||||
weights = new double[ClutGridPoints.Length];
|
||||
|
||||
for (int i = 0; i < weights.Length; i++)
|
||||
{
|
||||
double w = 1 * (ClutGridPoints[i]);
|
||||
for (int j = i + 1; j < weights.Length; j++)
|
||||
{
|
||||
w *= ClutGridPoints[j];
|
||||
}
|
||||
weights[i] = w;
|
||||
}
|
||||
}
|
||||
return weights;
|
||||
});
|
||||
|
||||
_processorPipeline = new Lazy<Func<double[], IccProfileHeader, double[]>[]>(BuildProcessorPipeline);
|
||||
}
|
||||
|
||||
public double[] ApplyMatrix(double[] values, IccProfileHeader header)
|
||||
{
|
||||
if (!HasMatrix)
|
||||
{
|
||||
throw new InvalidOperationException("No matrix.");
|
||||
}
|
||||
|
||||
if (Matrix is null)
|
||||
{
|
||||
throw new InvalidOperationException("Missing Matrix.");
|
||||
}
|
||||
|
||||
if (values.Length != 3)
|
||||
{
|
||||
throw new InvalidOperationException("Input values dimensions not correct.");
|
||||
}
|
||||
|
||||
double x1 = values[0];
|
||||
double x2 = values[1];
|
||||
double x3 = values[2];
|
||||
|
||||
// Check if identity, then do nothing
|
||||
|
||||
return new double[]
|
||||
{
|
||||
x1 * Matrix[0] + x2 * Matrix[1] + x3 * Matrix[2] + Matrix[9],
|
||||
x1 * Matrix[3] + x2 * Matrix[4] + x3 * Matrix[5] + Matrix[10],
|
||||
x1 * Matrix[6] + x2 * Matrix[7] + x3 * Matrix[8] + Matrix[11]
|
||||
};
|
||||
}
|
||||
|
||||
public double[] LookupClut(double[] input)
|
||||
{
|
||||
if (Clut == null || ClutGridPoints == null || _lookupWeights == null || _lookupWeights.Value == null)
|
||||
{
|
||||
throw new ArgumentNullException("No Clut.");
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/35109195/how-do-the-the-different-parts-of-an-icc-file-work-together
|
||||
if (input.Length != ClutGridPoints.Length)
|
||||
{
|
||||
throw new ArgumentException("TODO");
|
||||
}
|
||||
|
||||
// TODO - Need interpolation
|
||||
double index = 0;
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
index += input[i] * _lookupWeights.Value[i];
|
||||
}
|
||||
|
||||
return Clut[(int)index];
|
||||
}
|
||||
|
||||
private static double[] ProcessCurves(double[] input, IccBaseCurveType[] curves)
|
||||
{
|
||||
if (curves is null)
|
||||
{
|
||||
throw new InvalidOperationException("Missing curves.");
|
||||
}
|
||||
|
||||
if (input.Length != curves.Length)
|
||||
{
|
||||
throw new InvalidOperationException("TODO");
|
||||
}
|
||||
|
||||
// process
|
||||
double[] result = new double[input.Length];
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
result[i] = curves[i].Process(input[i]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private readonly Lazy<Func<double[], IccProfileHeader, double[]>[]> _processorPipeline;
|
||||
|
||||
public double[] Process(double[] values, IccProfileHeader header)
|
||||
{
|
||||
foreach (var func in _processorPipeline.Value)
|
||||
{
|
||||
values = func(values, header);
|
||||
}
|
||||
return values;
|
||||
|
||||
//switch (Type)
|
||||
//{
|
||||
// case LutABType.AB:
|
||||
// return ProcessAtoB(values, header);
|
||||
|
||||
// case LutABType.BA:
|
||||
// return ProcessBtoA(values, header);
|
||||
//}
|
||||
|
||||
//throw new ArgumentOutOfRangeException("Invalid type.");
|
||||
}
|
||||
|
||||
#region Build Processor Pipeline
|
||||
public Func<double[], IccProfileHeader, double[]>[] BuildProcessorPipeline()
|
||||
{
|
||||
switch (Type)
|
||||
{
|
||||
case LutABType.AB:
|
||||
return BuildProcessorPipelineAtoB();
|
||||
|
||||
case LutABType.BA:
|
||||
return BuildProcessorPipelineBtoA();
|
||||
}
|
||||
|
||||
throw new ArgumentOutOfRangeException("Invalid type.");
|
||||
}
|
||||
|
||||
private Func<double[], IccProfileHeader, double[]>[] BuildProcessorPipelineAtoB()
|
||||
{
|
||||
var pipeline = new List<Func<double[], IccProfileHeader, double[]>>();
|
||||
|
||||
if (HasACurve)
|
||||
{
|
||||
pipeline.Add((x, h) => ProcessCurves(x, ACurves));
|
||||
}
|
||||
|
||||
if (HasClut)
|
||||
{
|
||||
pipeline.Add((x, h) => LookupClut(x));
|
||||
}
|
||||
|
||||
if (HasMCurves)
|
||||
{
|
||||
pipeline.Add((x, h) => ProcessCurves(x, MCurves));
|
||||
}
|
||||
|
||||
if (HasMatrix)
|
||||
{
|
||||
pipeline.Add(ApplyMatrix);
|
||||
}
|
||||
|
||||
// BCurves
|
||||
pipeline.Add((x, h) => ProcessCurves(x, BCurves));
|
||||
return pipeline.ToArray();
|
||||
}
|
||||
|
||||
private Func<double[], IccProfileHeader, double[]>[] BuildProcessorPipelineBtoA()
|
||||
{
|
||||
var pipeline = new List<Func<double[], IccProfileHeader, double[]>>
|
||||
{
|
||||
(x, h) => ProcessCurves(x, BCurves)
|
||||
};
|
||||
|
||||
if (HasMatrix)
|
||||
{
|
||||
pipeline.Add(ApplyMatrix);
|
||||
}
|
||||
|
||||
if (HasClut)
|
||||
{
|
||||
pipeline.Add((x, h) => LookupClut(x));
|
||||
}
|
||||
|
||||
if (HasMCurves)
|
||||
{
|
||||
pipeline.Add((x, h) => ProcessCurves(x, MCurves));
|
||||
}
|
||||
|
||||
if (HasACurve)
|
||||
{
|
||||
pipeline.Add((x, h) => ProcessCurves(x, ACurves));
|
||||
}
|
||||
|
||||
return pipeline.ToArray();
|
||||
}
|
||||
#endregion
|
||||
|
||||
public IccRenderingIntent GetRenderingIntent(IccProfileClass profileClass, string tag)
|
||||
{
|
||||
if (profileClass == IccProfileClass.Abstract ||
|
||||
profileClass == IccProfileClass.DeviceLink ||
|
||||
profileClass == IccProfileClass.NamedColor)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
if (Type == LutABType.AB && tag != IccTags.AToB0Tag &&
|
||||
tag != IccTags.AToB1Tag && tag != IccTags.AToB2Tag)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
else if (Type == LutABType.AB && tag != IccTags.BToA0Tag &&
|
||||
tag != IccTags.BToA1Tag && tag != IccTags.BToA2Tag)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
switch (tag)
|
||||
{
|
||||
case IccTags.AToB0Tag:
|
||||
case IccTags.BToA0Tag:
|
||||
return IccRenderingIntent.Perceptual;
|
||||
|
||||
case IccTags.AToB1Tag:
|
||||
case IccTags.BToA1Tag:
|
||||
return IccRenderingIntent.MediaRelativeColorimetric; // TODO check
|
||||
|
||||
case IccTags.AToB2Tag:
|
||||
case IccTags.BToA2Tag:
|
||||
return IccRenderingIntent.Saturation;
|
||||
}
|
||||
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public static string GetProfileTag(IccProfileClass profileClass, IccRenderingIntent renderingIntent, LutABType lutABType)
|
||||
{
|
||||
|
||||
// See table 25
|
||||
switch (renderingIntent)
|
||||
{
|
||||
case IccRenderingIntent.Perceptual:
|
||||
switch (profileClass)
|
||||
{
|
||||
case IccProfileClass.Input:
|
||||
case IccProfileClass.Display:
|
||||
case IccProfileClass.Output:
|
||||
case IccProfileClass.ColorSpace:
|
||||
return lutABType == LutABType.AB ? IccTags.AToB0Tag : IccTags.BToA0Tag;
|
||||
}
|
||||
break;
|
||||
|
||||
case IccRenderingIntent.IccAbsoluteColorimetric: // TODO check
|
||||
case IccRenderingIntent.MediaRelativeColorimetric:
|
||||
switch (profileClass)
|
||||
{
|
||||
case IccProfileClass.Input:
|
||||
case IccProfileClass.Display:
|
||||
case IccProfileClass.Output:
|
||||
case IccProfileClass.ColorSpace:
|
||||
return lutABType == LutABType.AB ? IccTags.AToB1Tag : IccTags.BToA1Tag;
|
||||
}
|
||||
break;
|
||||
|
||||
case IccRenderingIntent.Saturation:
|
||||
switch (profileClass)
|
||||
{
|
||||
case IccProfileClass.Input:
|
||||
case IccProfileClass.Display:
|
||||
case IccProfileClass.Output:
|
||||
case IccProfileClass.ColorSpace:
|
||||
return lutABType == LutABType.AB ? IccTags.AToB2Tag : IccTags.BToA2Tag;
|
||||
}
|
||||
break;
|
||||
}
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public enum LutABType : byte
|
||||
{
|
||||
AB = 0,
|
||||
BA = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace IccProfileNet.Tags
|
||||
{
|
||||
internal sealed class IccMeasurementType : IccTagTypeBase
|
||||
{
|
||||
public const int StandardObserverOffset = 8;
|
||||
public const int StandardObserverLength = 4;
|
||||
public const int TristimulusOffset = 12;
|
||||
public const int TristimulusLength = 12;
|
||||
public const int MeasurementGeometryOffset = 24;
|
||||
public const int MeasurementGeometryLength = 4;
|
||||
public const int MeasurementFlareOffset = 28;
|
||||
public const int MeasurementFlareLength = 4;
|
||||
public const int StandardIlluminantOffset = 32;
|
||||
public const int StandardIlluminantLength = 4;
|
||||
|
||||
private readonly Lazy<string> _standardObserver;
|
||||
/// <summary>
|
||||
/// TODO
|
||||
/// </summary>
|
||||
public string StandardObserver => _standardObserver.Value;
|
||||
|
||||
private readonly Lazy<IccXyz> _tristimulus;
|
||||
/// <summary>
|
||||
/// TODO
|
||||
/// </summary>
|
||||
public IccXyz Tristimulus => _tristimulus.Value;
|
||||
|
||||
private readonly Lazy<string> _measurementGeometry;
|
||||
/// <summary>
|
||||
/// TODO
|
||||
/// </summary>
|
||||
public string MeasurementGeometry => _measurementGeometry.Value;
|
||||
|
||||
private readonly Lazy<string> _measurementFlare;
|
||||
/// <summary>
|
||||
/// TODO
|
||||
/// </summary>
|
||||
public string MeasurementFlare => _measurementFlare.Value;
|
||||
|
||||
private readonly Lazy<string> _standardIlluminant;
|
||||
/// <summary>
|
||||
/// TODO
|
||||
/// </summary>
|
||||
public string StandardIlluminant => _standardIlluminant.Value;
|
||||
|
||||
public IccMeasurementType(byte[] bytes)
|
||||
{
|
||||
string typeSignature = IccHelper.GetString(bytes, TypeSignatureOffset, TypeSignatureLength);
|
||||
|
||||
if (typeSignature != IccTags.MeasurementTag)
|
||||
{
|
||||
throw new ArgumentException(nameof(typeSignature));
|
||||
}
|
||||
|
||||
RawData = bytes;
|
||||
|
||||
_standardObserver = new Lazy<string>(() =>
|
||||
{
|
||||
// Encoded value for standard observer
|
||||
// 8 to 11
|
||||
byte[] standardObserverBytes = bytes
|
||||
.Skip(StandardObserverOffset)
|
||||
.Take(StandardObserverLength)
|
||||
.ToArray();
|
||||
string standardObserverHex = IccHelper.GetHexadecimalString(standardObserverBytes);
|
||||
|
||||
/*
|
||||
* Table 50 — Standard observer encodings (v4.4)
|
||||
* Standard observer Hex encoding
|
||||
* Unknown 00000000h
|
||||
* CIE 1931 standard colorimetric observer 00000001h
|
||||
* CIE 1964 standard colorimetric observer 00000002h
|
||||
*/
|
||||
|
||||
/*
|
||||
* Standard Observer Encoded Value
|
||||
* unknown 00000000h
|
||||
* 1931 2 degree Observer 00000001h
|
||||
* 1964 10 degree Observer 00000002h
|
||||
*/
|
||||
|
||||
switch (standardObserverHex)
|
||||
{
|
||||
case "00000000":
|
||||
return "Unknown";
|
||||
|
||||
case "00000001":
|
||||
return "1931";
|
||||
|
||||
case "00000002":
|
||||
return "1964";
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(standardObserverHex));
|
||||
}
|
||||
});
|
||||
|
||||
_tristimulus = new Lazy<IccXyz>(() =>
|
||||
{
|
||||
// nCIEXYZ tristimulus values for measurement backing
|
||||
// 12 to 23
|
||||
return IccHelper.ReadXyz(RawData
|
||||
.Skip(TristimulusOffset)
|
||||
.Take(TristimulusLength)
|
||||
.ToArray());
|
||||
});
|
||||
|
||||
_measurementGeometry = new Lazy<string>(() =>
|
||||
{
|
||||
// Encoded value for measurement geometry
|
||||
// 24 to 27
|
||||
byte[] measurementGeometry = RawData
|
||||
.Skip(MeasurementGeometryOffset)
|
||||
.Take(MeasurementGeometryLength)
|
||||
.ToArray();
|
||||
string measurementGeometryHex = IccHelper.GetHexadecimalString(measurementGeometry);
|
||||
/*
|
||||
* Table 51 — Measurement geometry encodings
|
||||
* Geometry Hex encoding
|
||||
* Unknown 00000000h
|
||||
* 0°:45° or 45°:0° 00000001h
|
||||
* 0°:d or d:0° 00000002h
|
||||
*/
|
||||
return measurementGeometryHex; // TODO
|
||||
});
|
||||
|
||||
_measurementFlare = new Lazy<string>(() =>
|
||||
{
|
||||
// Encoded value for measurement flare
|
||||
// 28 to 31
|
||||
byte[] measurementFlare = RawData
|
||||
.Skip(MeasurementFlareOffset)
|
||||
.Take(MeasurementFlareLength)
|
||||
.ToArray();
|
||||
string measurementFlareHex = IccHelper.GetHexadecimalString(measurementFlare);
|
||||
/*
|
||||
* Table 52 — Measurement flare encodings
|
||||
* Flare Hex encoding
|
||||
* 0 (0 %) 00000000h
|
||||
* 1,0 (or 100 %) 00010000h
|
||||
*/
|
||||
return measurementFlareHex; // TODO
|
||||
});
|
||||
|
||||
_standardIlluminant = new Lazy<string>(() =>
|
||||
{
|
||||
// Encoded value for standard illuminant
|
||||
// 32 to 35
|
||||
byte[] standardIlluminantBytes = bytes
|
||||
.Skip(StandardIlluminantOffset)
|
||||
.Take(StandardIlluminantLength)
|
||||
.ToArray();
|
||||
string standardIlluminantHex = IccHelper.GetHexadecimalString(standardIlluminantBytes);
|
||||
|
||||
/*
|
||||
* Table 53 — Standard illuminant encodings
|
||||
* Standard illuminant Hex encoding
|
||||
* Unknown 00000000h
|
||||
* D50 00000001h
|
||||
* D65 00000002h
|
||||
* D93 00000003h
|
||||
* F2 00000004h
|
||||
* D55 00000005h
|
||||
* A 00000006h
|
||||
* Equi-Power (E) 00000007h
|
||||
* F8 00000008h
|
||||
*/
|
||||
|
||||
switch (standardIlluminantHex)
|
||||
{
|
||||
case "00000000":
|
||||
return "Standard illuminant";
|
||||
|
||||
case "00000001":
|
||||
return "D50";
|
||||
|
||||
case "00000002":
|
||||
return "D65";
|
||||
|
||||
case "00000003":
|
||||
return "D93";
|
||||
|
||||
case "00000004":
|
||||
return "F2";
|
||||
|
||||
case "00000005":
|
||||
return "D55";
|
||||
|
||||
case "00000006":
|
||||
return "A";
|
||||
|
||||
case "00000007":
|
||||
return "Equi-Power (E)";
|
||||
|
||||
case "00000008":
|
||||
return "F8";
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(standardIlluminantHex));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace IccProfileNet.Tags
|
||||
{
|
||||
internal sealed class IccMultiLocalizedUnicodeType : IccTagTypeBase
|
||||
{
|
||||
public const int NumberOfRecordOffset = 8;
|
||||
public const int NumberOfRecordLength = 4;
|
||||
public const int RecordSizeOffset = 12;
|
||||
public const int RecordSizeLength = 4;
|
||||
|
||||
public const int FirstRecordLanguageOffset = 16;
|
||||
public const int RecordLanguageLength = 2;
|
||||
public const int FirstRecordCountryOffset = 18;
|
||||
public const int RecordCountryLength = 2;
|
||||
public const int FirstRecordLengthOffset = 20;
|
||||
public const int RecordLengthLength = 4;
|
||||
|
||||
public const int FirstRecordOffsetOffset = 24;
|
||||
public const int RecordOffsetLength = 4;
|
||||
|
||||
private readonly Lazy<int> _numberOfRecords;
|
||||
/// <summary>
|
||||
/// Number of records (n).
|
||||
/// </summary>
|
||||
public int NumberOfRecords => _numberOfRecords.Value;
|
||||
|
||||
private readonly Lazy<int> _recordSize;
|
||||
public int RecordSize => _recordSize.Value;
|
||||
|
||||
private readonly Lazy<IccMultiLocalizedUnicodeRecord[]> _records;
|
||||
/// <summary>
|
||||
/// TODO
|
||||
/// </summary>
|
||||
public IccMultiLocalizedUnicodeRecord[] Records => _records.Value;
|
||||
|
||||
public IccMultiLocalizedUnicodeType(byte[] rawData)
|
||||
{
|
||||
string typeSignature = IccHelper.GetString(rawData, TypeSignatureOffset, TypeSignatureLength);
|
||||
|
||||
if (typeSignature != "mluc")
|
||||
{
|
||||
throw new ArgumentException(nameof(typeSignature));
|
||||
}
|
||||
|
||||
RawData = rawData;
|
||||
|
||||
_numberOfRecords = new Lazy<int>(() =>
|
||||
{
|
||||
// Number of records (n)
|
||||
// 8 to 11
|
||||
return (int)IccHelper.ReadUInt32(RawData
|
||||
.Skip(NumberOfRecordOffset)
|
||||
.Take(NumberOfRecordLength)
|
||||
.ToArray());
|
||||
});
|
||||
|
||||
_recordSize = new Lazy<int>(() =>
|
||||
{
|
||||
// Record size: the length in bytes of every record. The value is 12.
|
||||
// 12 to 15
|
||||
int recordSize = (int)IccHelper.ReadUInt32(RawData
|
||||
.Skip(RecordSizeOffset)
|
||||
.Take(RecordSizeLength)
|
||||
.ToArray());
|
||||
|
||||
if (recordSize != 12)
|
||||
{
|
||||
throw new ArgumentException(nameof(recordSize));
|
||||
}
|
||||
return recordSize;
|
||||
});
|
||||
|
||||
_records = new Lazy<IccMultiLocalizedUnicodeRecord[]>(() =>
|
||||
{
|
||||
IccMultiLocalizedUnicodeRecord[] records = new IccMultiLocalizedUnicodeRecord[NumberOfRecords];
|
||||
|
||||
const int recordsStartOffset = RecordSizeOffset + RecordSizeLength;
|
||||
const int recordLanguageOffset = FirstRecordLanguageOffset - recordsStartOffset;
|
||||
const int recordCountryOffset = FirstRecordCountryOffset - recordsStartOffset;
|
||||
const int recordLengthOffset = FirstRecordLengthOffset - recordsStartOffset;
|
||||
const int recordOffsetOffset = FirstRecordOffsetOffset - recordsStartOffset;
|
||||
|
||||
for (var i = 0; i < NumberOfRecords; ++i)
|
||||
{
|
||||
int currentOffset = recordsStartOffset + (i * RecordSize);
|
||||
|
||||
// First record language code: in accordance with the
|
||||
// language code specified in ISO 639-1
|
||||
// 16 to 17
|
||||
string language = IccHelper.GetString(RawData
|
||||
.Skip(currentOffset + recordLanguageOffset)
|
||||
.Take(RecordLanguageLength)
|
||||
.ToArray());
|
||||
|
||||
// First record country code: in accordance with the country
|
||||
// code specified in ISO 3166-1
|
||||
// 18 to 19
|
||||
string country = IccHelper.GetString(RawData
|
||||
.Skip(currentOffset + recordCountryOffset)
|
||||
.Take(RecordCountryLength)
|
||||
.ToArray());
|
||||
|
||||
// First record string length: the length in bytes of the string
|
||||
// 20 to 23
|
||||
uint length = IccHelper.ReadUInt32(RawData
|
||||
.Skip(currentOffset + recordLengthOffset)
|
||||
.Take(RecordLengthLength)
|
||||
.ToArray());
|
||||
|
||||
// First record string offset: the offset from the start of the tag
|
||||
// to the start of the string, in bytes.
|
||||
// 24 to 27
|
||||
uint offset = IccHelper.ReadUInt32(RawData
|
||||
.Skip(currentOffset + recordOffsetOffset)
|
||||
.Take(RecordOffsetLength)
|
||||
.ToArray());
|
||||
|
||||
string text = IccHelper.GetString(RawData, (int)offset, (int)length);
|
||||
|
||||
records[i] = new IccMultiLocalizedUnicodeRecord(language, country, text);
|
||||
}
|
||||
return records;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TODO
|
||||
/// </summary>
|
||||
public readonly struct IccMultiLocalizedUnicodeRecord
|
||||
{
|
||||
/// <summary>
|
||||
/// Language code specified in ISO 639-1.
|
||||
/// </summary>
|
||||
public string Language { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Country code specified in ISO 3166-1.
|
||||
/// </summary>
|
||||
public string Country { get; }
|
||||
|
||||
/// <summary>
|
||||
/// TODO
|
||||
/// </summary>
|
||||
public string Text { get; }
|
||||
|
||||
/// <summary>
|
||||
/// TODO
|
||||
/// </summary>
|
||||
public IccMultiLocalizedUnicodeRecord(string language, string country, string text)
|
||||
{
|
||||
Language = language;
|
||||
Country = country;
|
||||
Text = text;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Country}-{Language}: {Text}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace IccProfileNet.Tags
|
||||
{
|
||||
internal sealed class IccParametricCurveType : IccBaseCurveType
|
||||
{
|
||||
public const int FunctionTypeOffset = 8;
|
||||
public const int FunctionTypeLength = 2;
|
||||
public const int ParametersOffset = 12;
|
||||
|
||||
public ushort FunctionType { get; }
|
||||
|
||||
private readonly Func<double, double> _func;
|
||||
private readonly Lazy<double> _g;
|
||||
private readonly Lazy<double> _a;
|
||||
private readonly Lazy<double> _b;
|
||||
private readonly Lazy<double> _c;
|
||||
private readonly Lazy<double> _d;
|
||||
private readonly Lazy<double> _e;
|
||||
private readonly Lazy<double> _f;
|
||||
|
||||
public IccParametricCurveType(byte[] rawData)
|
||||
{
|
||||
string typeSignature = IccHelper.GetString(rawData, TypeSignatureOffset, TypeSignatureLength);
|
||||
|
||||
if (typeSignature != "para")
|
||||
{
|
||||
throw new ArgumentException(nameof(typeSignature));
|
||||
}
|
||||
|
||||
RawData = rawData;
|
||||
Signature = "para";
|
||||
|
||||
// Encoded value of the function type
|
||||
// 8 to 9
|
||||
FunctionType = IccHelper.ReadUInt16(RawData
|
||||
.Skip(FunctionTypeOffset)
|
||||
.Take(FunctionTypeLength)
|
||||
.ToArray());
|
||||
|
||||
int parametersCount = GetParametersCount(FunctionType);
|
||||
|
||||
int fieldLength = parametersCount * 4;
|
||||
BytesRead = ParametersOffset + fieldLength;
|
||||
|
||||
_parameters = new Lazy<double[]>(() =>
|
||||
{
|
||||
// One or more parameters (see Table 67)
|
||||
// 12 to end
|
||||
return IccHelper.Reads15Fixed16Array(RawData
|
||||
.Skip(ParametersOffset)
|
||||
.Take(fieldLength)
|
||||
.ToArray());
|
||||
});
|
||||
|
||||
switch (FunctionType)
|
||||
{
|
||||
case 0:
|
||||
_g = new Lazy<double>(() => Parameters[0]);
|
||||
_func = x => Math.Pow(x, _g.Value);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
_g = new Lazy<double>(() => Parameters[0]);
|
||||
_a = new Lazy<double>(() => Parameters[1]);
|
||||
_b = new Lazy<double>(() => Parameters[2]);
|
||||
_func = x =>
|
||||
{
|
||||
if (x >= -_b.Value / _a.Value)
|
||||
{
|
||||
return Math.Pow(_a.Value * x + _b.Value, _g.Value);
|
||||
}
|
||||
return 0.0;
|
||||
};
|
||||
break;
|
||||
|
||||
case 2:
|
||||
_g = new Lazy<double>(() => Parameters[0]);
|
||||
_a = new Lazy<double>(() => Parameters[1]);
|
||||
_b = new Lazy<double>(() => Parameters[2]);
|
||||
_c = new Lazy<double>(() => Parameters[3]);
|
||||
_func = x =>
|
||||
{
|
||||
if (x >= -_b.Value / _a.Value)
|
||||
{
|
||||
return Math.Pow(_a.Value * x + _b.Value, _g.Value) + _c.Value;
|
||||
}
|
||||
return _c.Value;
|
||||
};
|
||||
break;
|
||||
|
||||
case 3:
|
||||
_g = new Lazy<double>(() => Parameters[0]);
|
||||
_a = new Lazy<double>(() => Parameters[1]);
|
||||
_b = new Lazy<double>(() => Parameters[2]);
|
||||
_c = new Lazy<double>(() => Parameters[3]);
|
||||
_d = new Lazy<double>(() => Parameters[4]);
|
||||
_func = x =>
|
||||
{
|
||||
if (x >= _d.Value)
|
||||
{
|
||||
return Math.Pow(_a.Value * x + _b.Value, _g.Value);
|
||||
}
|
||||
return _c.Value * x;
|
||||
};
|
||||
break;
|
||||
|
||||
case 4:
|
||||
_g = new Lazy<double>(() => Parameters[0]);
|
||||
_a = new Lazy<double>(() => Parameters[1]);
|
||||
_b = new Lazy<double>(() => Parameters[2]);
|
||||
_c = new Lazy<double>(() => Parameters[3]);
|
||||
_d = new Lazy<double>(() => Parameters[4]);
|
||||
_e = new Lazy<double>(() => Parameters[5]);
|
||||
_f = new Lazy<double>(() => Parameters[6]);
|
||||
_func = x =>
|
||||
{
|
||||
if (x >= _d.Value)
|
||||
{
|
||||
return Math.Pow(_a.Value * x + _b.Value, _g.Value) + _e.Value;
|
||||
}
|
||||
return _c.Value * x + _f.Value;
|
||||
};
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException($"Unknown Parametric Curve function type '{FunctionType}'. Allowed values are 1, 2, 3, 4.");
|
||||
}
|
||||
}
|
||||
|
||||
private static int GetParametersCount(int functionType)
|
||||
{
|
||||
// Table 68 — parametricCurveType function type encoding
|
||||
switch (functionType)
|
||||
{
|
||||
case 0:
|
||||
return 1;
|
||||
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
return functionType + 2;
|
||||
|
||||
case 4:
|
||||
return 7;
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException($"{functionType}");
|
||||
}
|
||||
}
|
||||
|
||||
public override double Process(double values)
|
||||
{
|
||||
return _func(values);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
string[] names = new string[] { "g", "a", "b", "c", "d", "e", "f" };
|
||||
string str = "(";
|
||||
|
||||
int i = 0;
|
||||
for (i = 0; i < Parameters.Length - 1; i++)
|
||||
{
|
||||
str += $"{names[i]}={Math.Round(Parameters[i], 4)},";
|
||||
}
|
||||
|
||||
str += $"{names[i]}={Math.Round(Parameters[i], 4)}";
|
||||
|
||||
return str + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace IccProfileNet.Tags
|
||||
{
|
||||
internal sealed class IccS15Fixed16ArrayType : IccTagTypeBase
|
||||
{
|
||||
public const int ArrayOffset = 8;
|
||||
|
||||
private readonly Lazy<double[]> values;
|
||||
public double[] Values => values.Value;
|
||||
|
||||
public IccS15Fixed16ArrayType(byte[] rawData)
|
||||
{
|
||||
string typeSignature = IccHelper.GetString(rawData, TypeSignatureOffset, TypeSignatureLength);
|
||||
|
||||
if (typeSignature != "sf32")
|
||||
{
|
||||
throw new ArgumentException(nameof(typeSignature));
|
||||
}
|
||||
|
||||
RawData = rawData;
|
||||
|
||||
values = new Lazy<double[]>(() =>
|
||||
{
|
||||
// An array of s15Fixed16Number values
|
||||
// 8 to end
|
||||
return IccHelper.Reads15Fixed16Array(RawData.Skip(ArrayOffset).ToArray());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace IccProfileNet.Tags
|
||||
{
|
||||
internal sealed class IccSignatureType : IccTagTypeBase
|
||||
{
|
||||
public const int SignatureOffset = 8;
|
||||
public const int SignatureLength = 4;
|
||||
|
||||
private readonly Lazy<string> _signature;
|
||||
/// <summary>
|
||||
/// TODO
|
||||
/// </summary>
|
||||
public string Signature => _signature.Value;
|
||||
|
||||
public IccSignatureType(byte[] rawData)
|
||||
{
|
||||
// TODO - check signature
|
||||
|
||||
RawData = rawData;
|
||||
|
||||
_signature = new Lazy<string>(() =>
|
||||
{
|
||||
// Encoded value for standard observer
|
||||
// 8 to 11
|
||||
byte[] signatureBytes = RawData
|
||||
.Skip(SignatureOffset)
|
||||
.Take(SignatureLength)
|
||||
.ToArray();
|
||||
|
||||
return IccHelper.GetString(signatureBytes);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
namespace IccProfileNet.Tags
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for ICC tage type.
|
||||
/// </summary>
|
||||
internal abstract class IccTagTypeBase
|
||||
{
|
||||
public const int TypeSignatureOffset = 0;
|
||||
public const int TypeSignatureLength = 4;
|
||||
public const int ReservedOffset = 4;
|
||||
public const int ReservedLength = 4;
|
||||
|
||||
/*
|
||||
/// <summary>
|
||||
/// Tag Signature.
|
||||
/// </summary>
|
||||
public string Signature { get; }
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Tag raw data.
|
||||
/// </summary>
|
||||
public byte[] RawData { get; protected set; }
|
||||
}
|
||||
}
|
||||
38
src/UglyToad.PdfPig/Graphics/Colors/ICC/Tags/IccTextType.cs
Normal file
38
src/UglyToad.PdfPig/Graphics/Colors/ICC/Tags/IccTextType.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace IccProfileNet.Tags
|
||||
{
|
||||
/// <summary>
|
||||
/// TODO
|
||||
/// </summary>
|
||||
internal sealed class IccTextType : IccTagTypeBase
|
||||
{
|
||||
public const int TextOffset = 8;
|
||||
|
||||
private readonly Lazy<string> _text;
|
||||
/// <summary>
|
||||
/// TODO
|
||||
/// </summary>
|
||||
public string Text => _text.Value;
|
||||
|
||||
public IccTextType(byte[] rawData)
|
||||
{
|
||||
string typeSignature = IccHelper.GetString(rawData, TypeSignatureOffset, TypeSignatureLength);
|
||||
|
||||
if (typeSignature != "text" && typeSignature != IccTags.ProfileDescriptionTag)
|
||||
{
|
||||
throw new ArgumentException(nameof(typeSignature));
|
||||
}
|
||||
|
||||
RawData = rawData;
|
||||
|
||||
_text = new Lazy<string>(() => IccHelper.GetString(RawData.Skip(TextOffset).ToArray()));
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Text;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using System;
|
||||
|
||||
namespace IccProfileNet.Tags
|
||||
{
|
||||
// PRivate tag instead of unknown tag
|
||||
internal sealed class IccUnknownTagType : IccTagTypeBase
|
||||
{
|
||||
public string Signature { get; }
|
||||
|
||||
private IccUnknownTagType(byte[] rawData, string signature)
|
||||
{
|
||||
RawData = rawData;
|
||||
Signature = signature;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse the bytes.
|
||||
/// </summary>
|
||||
public static IccTagTypeBase Parse(byte[] bytes)
|
||||
{
|
||||
string typeSignature = IccHelper.GetString(bytes, TypeSignatureOffset, TypeSignatureLength);
|
||||
|
||||
switch (typeSignature)
|
||||
{
|
||||
case "curv":
|
||||
return IccCurveType.Parse(bytes);
|
||||
|
||||
case "para":
|
||||
return IccParametricCurveType.Parse(bytes);
|
||||
|
||||
case "mft1":
|
||||
return IccLut8Type.Parse(bytes);
|
||||
|
||||
case "mft2":
|
||||
return IccLut16Type.Parse(bytes);
|
||||
|
||||
case "mAB ":
|
||||
case "mBA ":
|
||||
return new IccLutABType(bytes);
|
||||
|
||||
case IccTags.MeasurementTag:
|
||||
return new IccMeasurementType(bytes);
|
||||
|
||||
case "mluc":
|
||||
return new IccMultiLocalizedUnicodeType(bytes);
|
||||
|
||||
case "sf32":
|
||||
return new IccS15Fixed16ArrayType(bytes);
|
||||
|
||||
case "sig ":
|
||||
return new IccSignatureType(bytes);
|
||||
|
||||
case "text":
|
||||
case IccTags.ProfileDescriptionTag:
|
||||
return new IccTextType(bytes);
|
||||
|
||||
case IccTags.ViewingConditionsTag:
|
||||
return new IccViewingConditionsType(bytes);
|
||||
|
||||
case "XYZ ":
|
||||
return new IccXyzType(bytes);
|
||||
|
||||
case "dtim":
|
||||
return new IccDateTimeType(bytes);
|
||||
}
|
||||
|
||||
// TODO - implement others tags
|
||||
|
||||
throw new InvalidOperationException($"Invalid tag signature '{typeSignature}' for ICC profile.");
|
||||
//return new IccUnknownTagType(bytes, typeSignature);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace IccProfileNet.Tags
|
||||
{
|
||||
/// <summary>
|
||||
/// TODO
|
||||
/// </summary>
|
||||
internal sealed class IccViewingConditionsType : IccTagTypeBase
|
||||
{
|
||||
public const int IlluminantOffset = 8;
|
||||
public const int IlluminantLength = 12;
|
||||
|
||||
public const int SurroundOffset = 20;
|
||||
public const int SurroundLength = 12;
|
||||
|
||||
public const int IlluminantTypeOffset = 32;
|
||||
public const int IlluminantTypeLength = 4;
|
||||
|
||||
private readonly Lazy<IccXyz> illuminant;
|
||||
/// <summary>
|
||||
/// Un-normalized CIEXYZ values for illuminant (in which Y is in cd/m2).
|
||||
/// </summary>
|
||||
public IccXyz Illuminant => illuminant.Value;
|
||||
|
||||
private readonly Lazy<IccXyz> surround;
|
||||
/// <summary>
|
||||
/// Un-normalized CIEXYZ values for surround (in which Y is in cd/m2).
|
||||
/// </summary>
|
||||
public IccXyz Surround => surround.Value;
|
||||
|
||||
private readonly Lazy<byte[]> illuminantType;
|
||||
/// <summary>
|
||||
/// Illuminant type.
|
||||
/// </summary>
|
||||
public byte[] IlluminantType => illuminantType.Value;
|
||||
|
||||
public IccViewingConditionsType(byte[] rawData)
|
||||
{
|
||||
string typeSignature = IccHelper.GetString(rawData, TypeSignatureOffset, TypeSignatureLength);
|
||||
|
||||
if (typeSignature != IccTags.ViewingConditionsTag)
|
||||
{
|
||||
throw new ArgumentException(nameof(typeSignature));
|
||||
}
|
||||
|
||||
RawData = rawData;
|
||||
|
||||
illuminant = new Lazy<IccXyz>(() =>
|
||||
{
|
||||
// Un-normalized CIEXYZ values for illuminant (in which Y is in cd/m2)
|
||||
// 8 to 19
|
||||
return IccHelper.ReadXyz(RawData
|
||||
.Skip(IlluminantOffset)
|
||||
.Take(IlluminantLength)
|
||||
.ToArray());
|
||||
});
|
||||
|
||||
surround = new Lazy<IccXyz>(() =>
|
||||
{
|
||||
// Un-normalized CIEXYZ values for surround (in which Y is in cd/m2)
|
||||
// 20 to 31
|
||||
return IccHelper.ReadXyz(RawData
|
||||
.Skip(SurroundOffset)
|
||||
.Take(SurroundLength)
|
||||
.ToArray());
|
||||
});
|
||||
|
||||
illuminantType = new Lazy<byte[]>(() =>
|
||||
{
|
||||
// Illuminant type
|
||||
// 32 to 35
|
||||
// As described in measurementType
|
||||
return RawData
|
||||
.Skip(IlluminantTypeOffset)
|
||||
.Take(IlluminantTypeLength)
|
||||
.ToArray(); // TODO
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
46
src/UglyToad.PdfPig/Graphics/Colors/ICC/Tags/IccXyzType.cs
Normal file
46
src/UglyToad.PdfPig/Graphics/Colors/ICC/Tags/IccXyzType.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace IccProfileNet.Tags
|
||||
{
|
||||
/// <summary>
|
||||
/// XYZ type.
|
||||
/// </summary>
|
||||
internal sealed class IccXyzType : IccTagTypeBase
|
||||
{
|
||||
public const int XyzOffset = 8;
|
||||
|
||||
//public string Signature { get; internal set; }
|
||||
|
||||
private readonly Lazy<IccXyz> xyz;
|
||||
/// <summary>
|
||||
/// XYZ point.
|
||||
/// </summary>
|
||||
public IccXyz Xyz => xyz.Value;
|
||||
|
||||
public IccXyzType(byte[] rawData)
|
||||
{
|
||||
string typeSignature = IccHelper.GetString(rawData, TypeSignatureOffset, TypeSignatureLength);
|
||||
|
||||
if (typeSignature != "XYZ ")
|
||||
{
|
||||
throw new ArgumentException(nameof(typeSignature));
|
||||
}
|
||||
|
||||
RawData = rawData;
|
||||
|
||||
xyz = new Lazy<IccXyz>(() =>
|
||||
{
|
||||
return IccHelper.ReadXyz(RawData
|
||||
.Skip(XyzOffset)
|
||||
.ToArray());
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return Xyz.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@
|
||||
bytesPure = ColorSpaceDetailsByteConverter.Convert(image.ColorSpaceDetails, bytesPure,
|
||||
image.BitsPerComponent, image.WidthInSamples, image.HeightInSamples);
|
||||
|
||||
var numberOfComponents = image.ColorSpaceDetails.BaseNumberOfColorComponents;
|
||||
var numberOfComponents = image.ColorSpaceDetails.BaseType == ColorSpace.ICCBased ? 3 : image.ColorSpaceDetails.BaseNumberOfColorComponents;
|
||||
|
||||
var is3Byte = numberOfComponents == 3;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net45;net451;net452;net46;net461;net462;net47;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard2.0;net452;net46;net461;net462;net47;net6.0</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Version>0.1.8-alpha-001</Version>
|
||||
<IsTestProject>False</IsTestProject>
|
||||
@@ -38,4 +38,7 @@
|
||||
<ProjectReference Include="..\UglyToad.PdfPig.Tokenization\UglyToad.PdfPig.Tokenization.csproj" />
|
||||
<ProjectReference Include="..\UglyToad.PdfPig.Tokens\UglyToad.PdfPig.Tokens.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Graphics\Colors\Icc\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -258,7 +258,7 @@
|
||||
metadata = new XmpMetadata(metadataStream, filterProvider, scanner);
|
||||
}
|
||||
|
||||
return new ICCBasedColorSpaceDetails(numeric.Int, alternateColorSpaceDetails, range, metadata);
|
||||
return new ICCBasedColorSpaceDetails(numeric.Int, alternateColorSpaceDetails, range, metadata, streamToken.Decode(filterProvider, scanner));
|
||||
}
|
||||
case ColorSpace.Indexed:
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user