Failing to write tiles with YCBCR photometric in Libtiff.net - jpeg

I'm using Libtiff.net to write tiled images into a tiff file. It works ok if Photometric is set to RBG, but if I use YCbCr (in order to reduce file size), I get the error "Application trasferred too few scanlines". Relevant pieces of code:
private static Tiff CreateTiff()
{
var t = Tiff.Open(#"d:\test.tif", "w");
const long TIFF_SIZE = 256;
t.SetField(TiffTag.IMAGEWIDTH, TIFF_SIZE);
t.SetField(TiffTag.IMAGELENGTH, TIFF_SIZE);
t.SetField(TiffTag.TILEWIDTH, TIFF_SIZE);
t.SetField(TiffTag.TILELENGTH, TIFF_SIZE);
t.SetField(TiffTag.BITSPERSAMPLE, 8);
t.SetField(TiffTag.SAMPLESPERPIXEL, 3);
t.SetField(TiffTag.COMPRESSION, Compression.JPEG );
t.SetField(TiffTag.JPEGQUALITY, 80L);
t.SetField(TiffTag.PHOTOMETRIC, Photometric.YCBCR);
t.SetField(TiffTag.PLANARCONFIG, PlanarConfig.CONTIG);
return t;
}
private static void Go()
{
var tif = CreateTiff(true);
var bmp = Image.FromFile(#"d:\tile.bmp") as Bitmap;
var rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
var size = bmp.Width;
var bands = 3;
var bytes = new byte[size * size * bands];
var bmpdata = bmp.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
try
{
System.Runtime.InteropServices.Marshal.Copy(bmpdata.Scan0, bytes, 0, bytes.Length);
}
finally
{
bmp.UnlockBits(bmpdata);
bmp.Dispose();
}
tif.WriteEncodedTile(0, bytes, bytes.Length);
tif.Dispose();
Console.ReadLine();
}
This same code works if I use Photometric.RGB. Even if I write a stripped image (t.SetField(TiffTag.ROWSPERSTRIP, ROWS_PER_STRIP), tif.WriteEncodedStrip(count, bytes, bytes.Length)) using Photometric.YCbCr everything works just fine.
Why can't I use YCbCr with a tiled image? Thanks in advance.

I finally found a workaround. I can set the tiff as RGB, encode the pixel data as YCbCr and then write it. The size of the output image is greatly reduced, as I desired, and then I can recover RGB values when I read the tiles. But it still isn't a perfect solution: any other software will recognize this tiff as RBG, so the colors are incorrect.
I thought of editing the photometric tag after writing the data, but every software I've tried either ignores the command or corrupts the file. How can I modify this tag without corrupting the image?

Related

How can I load an exported Tileset (image collection) from Tiled in Phaser 3?

I want to load an image collection tileset into my phaser game. I know that with tilesets that are just one image you can just load that image into phaser, but what about an image collection? In Tiled I saw the options to export that tileset as either .tsx or .json, but I don't know if that helps in my case. The reason I need this is because I have some objects that are too big to be used as tiles. I can load them into Tiled and place them like I want to, but obviously they don't show up in my game, unless I can import that tileset into phaser. Does anyone know how to do that, or maybe you know a better option than using an image collection?
Well after some tests, and updating my Tiled version to 1.9.2, it seems there is an pretty simple way.
As long as the tileset collection is marked as "Embeded in map"
(I could have sworn, this checkbox was hidden/deactivated, when selecting "Collection of Images", in my early Tiled version)
Export the map as json
Load map and tile-images
preload() {
this.load.tilemapTiledJSON('map', 'export.tmj');
this.load.image('tile01', 'tile01.png');
this.load.image('tile02', 'tile02.png');
...
}
create Phaser TileSets, just use the filename from the json as the tilsetName (this is the "tricky" part, atleast for me)
create() {
var map = this.make.tilemap({ key: 'map' });
var img1 = map.addTilesetImage( 'tile01.png', 'tile01');
var img2 = map.addTilesetImage( 'tile02.png', 'tile02');
...
// create the layer with all tilesets
map.createLayer('Tile Layer 1', [img1, img2, ...]);
...
}
This should work, atleast with a "Collection of images", with images that have a size of 8x8 pixel (since I don't know the needed/intended Images size, I didn't want to waste time testing various images-sizes needlessly).
Here a small demo:
(due to CORS-issues the map data is inserted as jsonObject and the textures are generate and not loaded)
const mapJsonExport = {"compressionlevel":-1,"height":10,"infinite":false,"layers":[{"compression":"","data":"AQAAAAEAAAACAAAAAgAAAAEAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAIAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAACAAAAAQAAAAEAAAACAAAAAgAAAAEAAAABAAAAAgAAAAIAAAABAAAAAgAAAAIAAAACAAAAAgAAAAEAAAACAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAIAAAACAAAAAgAAAAEAAAACAAAAAgAAAAEAAAACAAAAAQAAAAEAAAACAAAAAQAAAAEAAAABAAAAAQAAAAEAAAACAAAAAgAAAAEAAAABAAAAAgAAAAEAAAABAAAAAQAAAAEAAAACAAAAAQAAAAIAAAACAAAAAgAAAAEAAAABAAAAAgAAAAEAAAACAAAAAgAAAAIAAAACAAAAAgAAAAEAAAABAAAAAgAAAAIAAAACAAAAAgAAAAEAAAACAAAAAQAAAA==","encoding":"base64","height":10,"id":1,"name":"Tile Layer 1","opacity":1,"type":"tilelayer","visible":true,"width":10,"x":0,"y":0}],"nextlayerid":2,"nextobjectid":1,"orientation":"orthogonal","renderorder":"right-down","tiledversion":"1.9.2","tileheight":8,"tilesets":[{"columns":0,"firstgid":1,"grid":{"height":1,"orientation":"orthogonal","width":1},"margin":0,"name":"tiles","spacing":0,"tilecount":2,"tileheight":8,"tiles":[{"id":0,"image":"tile01.png","imageheight":8,"imagewidth":8},{"id":1,"image":"tile02.png","imageheight":8,"imagewidth":8}],"tilewidth":8}],"tilewidth":8,"type":"map","version":"1.9","width":10};
var config = {
width: 8 * 10,
height: 8 * 10,
zoom: 2.2,
scene: { preload, create }
};
function preload() {
// loading inline JSON due to CORS-issues with the code Snippets
this.load.tilemapTiledJSON('map', mapJsonExport);
// generating texture instead of loading them due to CORS-issues with the code Snippets
let graphics = this.make.graphics({add: false});
graphics.fillStyle(0xff0000);
graphics.fillRect(0, 0, 8, 8);
graphics.generateTexture('tile01', 8, 8);
graphics.fillStyle(0x000000);
graphics.fillRect(0, 0, 8, 8);
graphics.generateTexture('tile02', 8, 8);
}
function create () {
let map = this.make.tilemap({ key: 'map' });
let img1 = map.addTilesetImage( 'tile01.png', 'tile01');
let img2 = map.addTilesetImage( 'tile02.png', 'tile02');
map.createLayer('Tile Layer 1', [img1, img2], 0, 0);
}
new Phaser.Game(config);
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>

How can I get PDF dimensions in pixels in Node.js?

I tried pdf2json:
const PDFParser = require("pdf2json");
let pdfParser = new PDFParser();
pdfParser.loadPDF("./30x40.pdf"); // ex: ./abc.pdf
pdfParser.on("pdfParser_dataReady", pdfData => {
width = pdfData.formImage.Width; // pdf width
height = pdfData.formImage.Pages[0].Height; // page height
console.log(`Height : ${height}`) // logs 70.866
console.log(`Width : ${width}`) // logs 53.15
});
But it gave the dimensions in a unknown units!
The dimensions in pixels will help me include them in the pdf-poppler module that converts a pdf file to an image and it needs the pdf file height in pixels.
Try calipers.
Code example:
const Calipers = require('calipers')('png', 'pdf');
Calipers.measure('./30x40.pdf')
.then(data => {
const { width, height } = data.pages[0];
});
Alternatively, try a module which converts it without needing width/height:
pdf2pic pdf-image node-imagemagick
If you're set on using pdf2json, please read this bit of documentation describing the units of the output.
Bit late to the party, but as discussed here: stackoverflow pdf2json unit
you can multiply your width and height by 24
Just like:
width = pdfData.formImage.Width * 24; // pdf width
height = pdfData.formImage.Pages[0].Height * 24; // page height
and you get Pixel.

Create a grid on top of an image

I hope you are all well and safe.
In NodeJS, I wanted to create a grid on top of an image. Like this:
Image without grid
Image with grid
Can someone tell me, please, how can I achieve this (some library or something)?
After creating the grid, I would like to go square by square and check the information for each square. Does anyone have any ideas?
Thank you very much for your time!
The first answer has a native Cairo dependency... Below I used pureimage instead, which has Pure JS implementations of jpeg and png encoding.
static drawParallel = (canvas, step, isYAxis) => {
const c2d = canvas.getContext('2d')
const numberOfSteps = (canvas.width / step) | 0
const end = isYAxis ? canvas.height : canvas.width
console.log(`Steps: ${numberOfSteps}\n`)
c2d.lineWidth = 1.1 // PureImage hides thin lines
c2d.strokeStyle = 'rgba(255,192,203,0.69)'
for (let i = 1; i < numberOfSteps; i++) {
const from = i * step
const to = i * step
const mx = isYAxis ? [from, 0, to, end] : [0, from, end, to]
console.log(`Stroking ${mx[0]}, ${mx[1]} to ${mx[2]}, ${mx[3]}`)
c2d.beginPath()
c2d.moveTo(mx[0], mx[1])
c2d.lineTo(mx[2], mx[3])
c2d.stroke()
}
}
source: https://stackblitz.com/edit/feathersjs-7kqlyt
I would use Canvas. You can use it for all sorts of image jobs and editing. For example, you could get a transparent PNG image of the grid and lay it on:
const Canvas = require("canvas");
const canvas = Canvas.createCanvas(619, 319);
const ctx = canvas.getContext("2d");
let img = await Canvas.loadImage("./path/to/image.png");
ctx.drawImage(img, 0, 0, img.width, img.height);
let grid = await Canvas.loadImage("./path/to/grid.png");
ctx.drawImage(grid, 0, 0, canvas.width, canvas.height);
console.log(canvas.toDataURL());
I myself managed to get something like this.

Three.js doesn't fill shape with custom texture/color spectrum

I have a requirement to draw a polygon and fill it with custom color spectrum.
For color spectrum: after google, I found this post which shows me how to generate a color spectrum the way I need.
For polygon: I choose THREE.Shape() so I can define where to draw it.
Put things together: it works fine with mono color like material = new THREE.MeshBasicMaterial({ color: 0xff0000, side: THREE.DoubleSide }); but it shows nothing when I use color spectrum. You can find my code here (which I forked from here).
Please point me where I do thing wrong.
To not waste your time viewing code, here is snippet:
var curveShape = new THREE.Shape();
curveShape.moveTo(0, 0);
curveShape.lineTo(5, 7);
curveShape.lineTo(2, 9);
curveShape.lineTo(8, 11);
curveShape.lineTo(10, 15);
curveShape.lineTo(9, 16);
curveShape.lineTo(7, 20);
curveShape.lineTo(0, 20);
// geometry
var geometry = new THREE.ShapeGeometry(curveShape);
// material texture
var texture = new THREE.Texture(generateTexture());
texture.needsUpdate = true; // important!
// material
var material = new THREE.MeshBasicMaterial({
map: texture,
overdraw: 0.5,
side: THREE.DoubleSide
});
// mesh
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
The problem is that you are just using moveTo() in order to draw your shape. But this method only sets the internal current point of the shape to the given coordinates. To create a real contour, use methods like lineTo().
var curveShape = new THREE.Shape();
curveShape.moveTo(0, 0); // define start point
curveShape.lineTo(5, 7); // draw a line from the current point to the given coordindates
Full example based on your code snippet: https://jsfiddle.net/f2Lommf5/12600/

Draw Threejs TextGeometry along the path

I have some model and want cover it with text.
I've rendered and bended TextGeometry but it is difficult to combine these two meshes.
(Аnd yes, I've tried the dynamic textures, this way prohibits the use of own fonts)
scrinshot of existing model
Perhaps there is another way to draw the text along the path?
As you want to cover it with text, why not to use textures.
You can set it from a picture with THREE.TextureLoader() or you can draw your own on a canvas and apply it to a texture with var texture = new THREE.Texture(canvas);
For exmaple:
var texture = new THREE.Texture(canvas);
texture.repeat.set(5, 1);
texture.needsUpdate = true;
See the jsfiddle example.
There you can uncomment those lines
//texture.wrapS = THREE.RepeatWrapping;
//texture.wrapT = THREE.RepeatWrapping;
and see, how the result will change.
UPD. I've updated the fiddle. Used the trick with WebFontLoader (from this SO)
WebFontConfig = {
google: {families: ['Monoton']},
active: function() {
init();
animate();
},
};
(function(){
var wf = document.createElement("script");
wf.src = 'https://cdnjs.cloudflare.com/ajax/libs/webfont/1.6.26/webfontloader.js';
wf.async = 'true';
document.head.appendChild(wf);
})();

Resources