DEV Community

Hardi
Hardi

Posted on

PNG to TIFF Converter: Professional Image Handling for Document Processing and Archival

While most developers focus on web-friendly formats like PNG, JPEG, and WebP, there's an entire world of professional image processing that relies heavily on TIFF. Whether you're building document management systems, medical imaging applications, or archival solutions, understanding PNG to TIFF conversion is crucial.

Let's explore why TIFF matters, when to use it, and how to implement conversion in your applications.

Why TIFF Format Still Dominates Professional Industries

What Makes TIFF Special

TIFF (Tagged Image File Format):
├─ Lossless compression (or uncompressed)
├─ Supports multiple pages in one file
├─ Handles enormous resolutions (gigapixels)
├─ Stores extensive metadata
├─ Multiple color spaces (RGB, CMYK, LAB, Grayscale)
├─ Alpha channels and transparency
└─ Industry-standard for professional work
Enter fullscreen mode Exit fullscreen mode

TIFF vs PNG: The Key Differences

// PNG characteristics
const pngFeatures = {
  compression: 'lossless (deflate)',
  pages: 'single page only',
  colorSpaces: ['RGB', 'Grayscale', 'Indexed'],
  maxSize: '~2GB practical limit',
  metadata: 'basic (limited)',
  transparency: 'yes (alpha channel)',
  useCase: 'web, general purpose'
};

// TIFF characteristics
const tiffFeatures = {
  compression: 'multiple options (LZW, ZIP, JPEG, uncompressed)',
  pages: 'multi-page support',
  colorSpaces: ['RGB', 'CMYK', 'LAB', 'Grayscale', 'indexed'],
  maxSize: '4GB+ (BigTIFF for larger)',
  metadata: 'extensive (EXIF, IPTC, XMP)',
  transparency: 'yes (alpha channel)',
  useCase: 'professional, archival, printing, medical'
};
Enter fullscreen mode Exit fullscreen mode

When You Need PNG to TIFF Conversion

1. Document Management Systems

// Scenario: Multi-page document scanning system
class DocumentScanner {
  constructor() {
    this.pages = [];
  }

  async addPage(pngPath) {
    this.pages.push(pngPath);
  }

  async generateMultiPageTiff(outputPath) {
    // Convert multiple PNGs to single multi-page TIFF
    await this.convertToMultiPageTiff(this.pages, outputPath);
    console.log(`Created ${this.pages.length}-page TIFF: ${outputPath}`);
  }
}

// Use case: Scanning legal documents, contracts, invoices
const scanner = new DocumentScanner();
await scanner.addPage('page1.png');
await scanner.addPage('page2.png');
await scanner.addPage('page3.png');
await scanner.generateMultiPageTiff('contract.tiff');
Enter fullscreen mode Exit fullscreen mode

2. Medical Imaging Applications

# DICOM to TIFF conversion for archival
import pydicom
from PIL import Image
import numpy as np

def medical_image_to_tiff(dicom_path, tiff_output):
    """
    Convert medical images to TIFF for long-term storage
    TIFF preferred over PNG for:
    - 16-bit grayscale support
    - Extensive metadata preservation
    - Industry compliance
    """
    ds = pydicom.dcmread(dicom_path)
    image_array = ds.pixel_array

    # Preserve 16-bit depth (PNG limited to 8-bit for grayscale)
    img = Image.fromarray(image_array)

    # Save with metadata
    img.save(tiff_output, 
             compression='tiff_adobe_deflate',  # Lossless
             dpi=(ds.get('PixelSpacing', [1, 1])))

    print(f"Medical image saved: {tiff_output}")
Enter fullscreen mode Exit fullscreen mode

3. Print Production Workflows

// Scenario: Preparing images for professional printing
async function prepareForPrint(pngPath, tiffOutput) {
  await sharp(pngPath)
    .tiff({
      compression: 'lzw',      // Lossless compression
      quality: 100,
      xres: 300,               // 300 DPI for print
      yres: 300,
      resolutionUnit: 'inch'
    })
    .toFile(tiffOutput);

  console.log('Print-ready TIFF created at 300 DPI');
}

// Print shops prefer TIFF because:
// - CMYK color space support
// - No quality loss
// - Embedded color profiles
// - Industry standard format
Enter fullscreen mode Exit fullscreen mode

4. Archival and Digital Preservation

// Long-term archival system
class ArchivalSystem {
  async archiveImage(pngPath, metadata) {
    const archivalTiff = await this.convertToArchivalTiff(pngPath);

    // TIFF preferred for archives because:
    // - Lossless preservation
    // - Extensive metadata storage
    // - Wide software support
    // - Decades of proven stability

    await this.storeWithMetadata(archivalTiff, {
      ...metadata,
      format: 'TIFF',
      compression: 'none',  // Uncompressed for maximum longevity
      created: new Date(),
      checksums: await this.calculateChecksums(archivalTiff)
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

5. Geographic Information Systems (GIS)

# GIS applications use GeoTIFF (TIFF with geographic data)
from osgeo import gdal
import numpy as np

def create_geotiff(png_path, geotiff_output, geo_transform, projection):
    """
    Convert PNG to GeoTIFF for GIS applications
    GeoTIFF stores geographic coordinates and projection data
    """
    # Open PNG
    png_ds = gdal.Open(png_path)

    # Create GeoTIFF
    driver = gdal.GetDriverByName('GTiff')
    tiff_ds = driver.CreateCopy(geotiff_output, png_ds, options=[
        'COMPRESS=LZW',
        'TILED=YES',
        'BIGTIFF=IF_SAFER'
    ])

    # Add geographic metadata
    tiff_ds.SetGeoTransform(geo_transform)
    tiff_ds.SetProjection(projection)

    tiff_ds = None  # Close and save
    print(f"GeoTIFF created: {geotiff_output}")
Enter fullscreen mode Exit fullscreen mode

Implementation Methods

1. Command Line with ImageMagick

# Basic conversion
convert input.png output.tiff

# With compression (LZW - lossless)
convert input.png -compress lzw output.tiff

# High-quality with specific DPI
convert input.png -compress lzw -density 300 output.tiff

# Multi-page TIFF from multiple PNGs
convert page1.png page2.png page3.png -compress lzw output.tiff

# Uncompressed (for archival)
convert input.png -compress none output.tiff

# With specific color depth
convert input.png -depth 16 -compress lzw output.tiff

# CMYK color space for print
convert input.png -colorspace CMYK -compress lzw output.tiff
Enter fullscreen mode Exit fullscreen mode

2. Node.js Implementation with Sharp

const sharp = require('sharp');
const fs = require('fs').promises;

async function pngToTiff(inputPath, outputPath, options = {}) {
  try {
    const {
      compression = 'lzw',  // lzw, deflate, jpeg, none
      quality = 100,
      xres = 72,
      yres = 72,
      depth = 8
    } = options;

    await sharp(inputPath)
      .tiff({
        compression,
        quality,
        xres,
        yres,
        resolutionUnit: 'inch'
      })
      .toFile(outputPath);

    console.log(`✓ TIFF created: ${outputPath}`);
    return outputPath;
  } catch (error) {
    console.error('Conversion error:', error);
    throw error;
  }
}

// Usage examples
await pngToTiff('photo.png', 'photo.tiff');

// High-quality print version
await pngToTiff('design.png', 'design-print.tiff', {
  compression: 'lzw',
  quality: 100,
  xres: 300,
  yres: 300
});

// Archival version (uncompressed)
await pngToTiff('document.png', 'archive.tiff', {
  compression: 'none',
  xres: 600,
  yres: 600
});
Enter fullscreen mode Exit fullscreen mode

3. Multi-Page TIFF Creation

const sharp = require('sharp');
const { exec } = require('child_process');
const util = require('util');
const execPromise = util.promisify(exec);

async function createMultiPageTiff(pngPaths, outputPath) {
  try {
    // Convert each PNG to TIFF first
    const tempTiffs = [];

    for (let i = 0; i < pngPaths.length; i++) {
      const tempTiff = `temp_page_${i}.tiff`;
      await sharp(pngPaths[i])
        .tiff({ compression: 'lzw' })
        .toFile(tempTiff);
      tempTiffs.push(tempTiff);
    }

    // Combine into multi-page TIFF using ImageMagick
    const command = `convert ${tempTiffs.join(' ')} -compress lzw ${outputPath}`;
    await execPromise(command);

    // Cleanup temp files
    for (const temp of tempTiffs) {
      await fs.unlink(temp);
    }

    console.log(`✓ Multi-page TIFF created with ${pngPaths.length} pages`);
    return outputPath;
  } catch (error) {
    console.error('Multi-page conversion error:', error);
    throw error;
  }
}

// Usage
const pages = ['scan1.png', 'scan2.png', 'scan3.png'];
await createMultiPageTiff(pages, 'document.tiff');
Enter fullscreen mode Exit fullscreen mode

4. Express API Endpoint

const express = require('express');
const multer = require('multer');
const sharp = require('sharp');
const path = require('path');

const app = express();
const upload = multer({ 
  storage: multer.memoryStorage(),
  limits: { fileSize: 50 * 1024 * 1024 } // 50MB limit
});

app.post('/api/convert-to-tiff', upload.single('image'), async (req, res) => {
  try {
    if (!req.file) {
      return res.status(400).json({ error: 'No file uploaded' });
    }

    const options = {
      compression: req.body.compression || 'lzw',
      quality: parseInt(req.body.quality) || 100,
      xres: parseInt(req.body.dpi) || 300,
      yres: parseInt(req.body.dpi) || 300
    };

    const tiffBuffer = await sharp(req.file.buffer)
      .tiff(options)
      .toBuffer();

    // Send as download
    res.set({
      'Content-Type': 'image/tiff',
      'Content-Disposition': `attachment; filename="${path.parse(req.file.originalname).name}.tiff"`,
      'Content-Length': tiffBuffer.length
    });

    res.send(tiffBuffer);

    console.log(`✓ Converted: ${req.file.originalname} -> TIFF (${(tiffBuffer.length / 1024).toFixed(2)}KB)`);
  } catch (error) {
    console.error('Conversion error:', error);
    res.status(500).json({ error: 'Conversion failed', details: error.message });
  }
});

// Multi-page endpoint
app.post('/api/convert-to-multipage-tiff', upload.array('images', 50), async (req, res) => {
  try {
    if (!req.files || req.files.length === 0) {
      return res.status(400).json({ error: 'No files uploaded' });
    }

    const tempFiles = [];

    // Convert each to TIFF
    for (let i = 0; i < req.files.length; i++) {
      const tempPath = `temp_${Date.now()}_${i}.tiff`;
      await sharp(req.files[i].buffer)
        .tiff({ compression: 'lzw' })
        .toFile(tempPath);
      tempFiles.push(tempPath);
    }

    // Combine using ImageMagick
    const outputPath = `output_${Date.now()}.tiff`;
    await execPromise(`convert ${tempFiles.join(' ')} ${outputPath}`);

    // Send file
    res.download(outputPath, 'document.tiff', async (err) => {
      // Cleanup
      for (const temp of tempFiles) {
        await fs.unlink(temp).catch(() => {});
      }
      await fs.unlink(outputPath).catch(() => {});
    });
  } catch (error) {
    console.error('Multi-page conversion error:', error);
    res.status(500).json({ error: 'Conversion failed' });
  }
});

app.listen(3000, () => {
  console.log('TIFF conversion API running on port 3000');
});
Enter fullscreen mode Exit fullscreen mode

5. Python Implementation with Pillow

from PIL import Image, TiffImagePlugin
import os

def png_to_tiff(input_path, output_path, compression='tiff_lzw', dpi=300):
    """
    Convert PNG to TIFF with various options

    Compression options:
    - 'tiff_lzw': Lossless compression (recommended)
    - 'tiff_adobe_deflate': Alternative lossless
    - 'tiff_raw': No compression
    - 'jpeg': Lossy compression (not recommended)
    """
    try:
        img = Image.open(input_path)

        # Convert RGBA to RGB if needed (TIFF handles both)
        if img.mode == 'RGBA':
            # TIFF supports transparency, but some apps prefer RGB
            background = Image.new('RGB', img.size, (255, 255, 255))
            background.paste(img, mask=img.split()[3])  # Use alpha as mask
            img = background

        # Save with options
        img.save(
            output_path,
            format='TIFF',
            compression=compression,
            dpi=(dpi, dpi)
        )

        print(f"✓ Converted: {input_path} -> {output_path}")

        # Display file sizes
        input_size = os.path.getsize(input_path) / 1024
        output_size = os.path.getsize(output_path) / 1024
        print(f"  Input: {input_size:.2f}KB -> Output: {output_size:.2f}KB")

        return output_path
    except Exception as e:
        print(f"✗ Conversion failed: {e}")
        raise

# Usage examples
png_to_tiff('photo.png', 'photo.tiff')

# High-resolution archival
png_to_tiff('document.png', 'archive.tiff', compression='tiff_raw', dpi=600)

# Print-ready
png_to_tiff('design.png', 'print.tiff', compression='tiff_lzw', dpi=300)
Enter fullscreen mode Exit fullscreen mode

6. Multi-Page TIFF with Python

from PIL import Image
import os

def create_multipage_tiff(png_list, output_path, compression='tiff_lzw'):
    """
    Create multi-page TIFF from multiple PNGs
    """
    if not png_list:
        raise ValueError("No PNG files provided")

    # Open first image
    images = []
    first_image = Image.open(png_list[0])

    # Open remaining images
    for png_path in png_list[1:]:
        img = Image.open(png_path)
        images.append(img)

    # Save as multi-page TIFF
    first_image.save(
        output_path,
        format='TIFF',
        compression=compression,
        save_all=True,
        append_images=images
    )

    print(f"✓ Created {len(png_list)}-page TIFF: {output_path}")

    # Cleanup
    for img in images:
        img.close()
    first_image.close()

# Usage
pages = ['page1.png', 'page2.png', 'page3.png', 'page4.png']
create_multipage_tiff(pages, 'document.tiff')
Enter fullscreen mode Exit fullscreen mode

7. Quick Online Conversion

For rapid development or one-off conversions where setting up a conversion pipeline isn't practical, using a PNG to TIFF converter can speed up your workflow. This is particularly useful when:

  • Testing formats: Evaluating TIFF vs PNG for your use case
  • Client requirements: Converting files provided by clients
  • Prototyping: Quickly generating TIFF files for testing
  • Emergency conversions: When your local tools aren't working

Once you've validated TIFF works for your application, implement automated conversion in your production pipeline.

Advanced Features and Optimization

1. Compression Comparison

const sharp = require('sharp');
const fs = require('fs');

async function compareCompressions(inputPath) {
  const compressions = ['none', 'lzw', 'deflate', 'jpeg'];

  console.log('Compression Comparison:');
  console.log('======================');

  for (const compression of compressions) {
    const output = `test_${compression}.tiff`;

    const startTime = Date.now();
    await sharp(inputPath)
      .tiff({ 
        compression,
        quality: compression === 'jpeg' ? 90 : 100
      })
      .toFile(output);
    const duration = Date.now() - startTime;

    const size = fs.statSync(output).size;
    const sizeKB = (size / 1024).toFixed(2);

    console.log(`${compression.padEnd(10)}: ${sizeKB.padStart(10)}KB  (${duration}ms)`);

    // Cleanup
    fs.unlinkSync(output);
  }
}

// Results example (1920x1080 photo):
// none      :   6234.15KB  (45ms)
// lzw       :   3456.78KB  (89ms)  <- Best for lossless
// deflate   :   3123.45KB  (112ms)
// jpeg      :   1234.56KB  (67ms)  <- Lossy!
Enter fullscreen mode Exit fullscreen mode

2. Metadata Preservation

const sharp = require('sharp');
const exifReader = require('exif-reader');

async function convertWithMetadata(inputPath, outputPath) {
  // Read PNG metadata
  const metadata = await sharp(inputPath).metadata();

  console.log('Original metadata:');
  console.log('- Format:', metadata.format);
  console.log('- Width:', metadata.width);
  console.log('- Height:', metadata.height);
  console.log('- Color space:', metadata.space);
  console.log('- Has alpha:', metadata.hasAlpha);

  // Convert to TIFF preserving metadata
  await sharp(inputPath)
    .withMetadata()  // Preserve EXIF, ICC profile, etc.
    .tiff({
      compression: 'lzw',
      xres: metadata.density || 72,
      yres: metadata.density || 72
    })
    .toFile(outputPath);

  console.log(`✓ Metadata preserved in ${outputPath}`);
}
Enter fullscreen mode Exit fullscreen mode

3. Color Space Conversion

// Convert to CMYK for print production
async function convertToCMYK(pngPath, tiffOutput) {
  // Note: Sharp doesn't directly support CMYK
  // Use ImageMagick for CMYK conversion
  const { exec } = require('child_process');
  const util = require('util');
  const execPromise = util.promisify(exec);

  await execPromise(
    `convert ${pngPath} -colorspace CMYK -compress lzw ${tiffOutput}`
  );

  console.log('✓ Converted to CMYK TIFF for print');
}

// Convert to grayscale
async function convertToGrayscale(pngPath, tiffOutput) {
  await sharp(pngPath)
    .greyscale()
    .tiff({
      compression: 'lzw',
      xres: 300,
      yres: 300
    })
    .toFile(tiffOutput);

  console.log('✓ Converted to grayscale TIFF');
}
Enter fullscreen mode Exit fullscreen mode

4. Batch Processing

const glob = require('glob');
const path = require('path');

async function batchConvertDirectory(inputDir, outputDir) {
  const pngFiles = glob.sync(`${inputDir}/**/*.png`);

  console.log(`Found ${pngFiles.length} PNG files to convert`);

  for (let i = 0; i < pngFiles.length; i++) {
    const inputPath = pngFiles[i];
    const relativePath = path.relative(inputDir, inputPath);
    const outputPath = path.join(
      outputDir, 
      relativePath.replace('.png', '.tiff')
    );

    // Ensure output directory exists
    const outputDirPath = path.dirname(outputPath);
    await fs.mkdir(outputDirPath, { recursive: true });

    await pngToTiff(inputPath, outputPath, {
      compression: 'lzw',
      xres: 300,
      yres: 300
    });

    console.log(`[${i + 1}/${pngFiles.length}] Converted: ${relativePath}`);
  }

  console.log('✓ Batch conversion complete');
}

// Usage
await batchConvertDirectory('./input_images', './output_tiffs');
Enter fullscreen mode Exit fullscreen mode

Build Pipeline Integration

Gulp Task

const gulp = require('gulp');
const through2 = require('through2');
const sharp = require('sharp');

gulp.task('convert-to-tiff', () => {
  return gulp.src('src/images/**/*.png')
    .pipe(through2.obj(async (file, _, cb) => {
      if (file.isBuffer()) {
        try {
          const tiffBuffer = await sharp(file.contents)
            .tiff({
              compression: 'lzw',
              quality: 100,
              xres: 300,
              yres: 300
            })
            .toBuffer();

          file.contents = tiffBuffer;
          file.extname = '.tiff';

          cb(null, file);
        } catch (error) {
          cb(error);
        }
      } else {
        cb(null, file);
      }
    }))
    .pipe(gulp.dest('dist/images'));
});
Enter fullscreen mode Exit fullscreen mode

Webpack Loader

// tiff-loader.js
const sharp = require('sharp');

module.exports = async function(content) {
  const callback = this.async();

  try {
    const tiffBuffer = await sharp(content)
      .tiff({ compression: 'lzw' })
      .toBuffer();

    callback(null, tiffBuffer);
  } catch (error) {
    callback(error);
  }
};

module.exports.raw = true;  // Handle binary data

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.png$/,
        use: [
          {
            loader: path.resolve('tiff-loader.js'),
            options: {
              compression: 'lzw',
              quality: 100
            }
          }
        ]
      }
    ]
  }
};
Enter fullscreen mode Exit fullscreen mode

Use Case: Document Management System

class DocumentManagementSystem {
  constructor(storagePath) {
    this.storagePath = storagePath;
  }

  async uploadDocument(files) {
    // Convert uploaded PNGs to multi-page TIFF
    const documentId = this.generateDocumentId();
    const tiffPath = path.join(this.storagePath, `${documentId}.tiff`);

    await createMultiPageTiff(files, tiffPath);

    // Extract metadata
    const metadata = await this.extractMetadata(tiffPath);

    // Store in database
    await this.storeDocument({
      id: documentId,
      path: tiffPath,
      pageCount: files.length,
      format: 'TIFF',
      compression: 'LZW',
      created: new Date(),
      ...metadata
    });

    console.log(`✓ Document archived: ${documentId}`);
    return documentId;
  }

  async retrieveDocument(documentId, pageNumber = null) {
    const doc = await this.getDocumentFromDB(documentId);

    if (pageNumber) {
      // Extract specific page from multi-page TIFF
      return await this.extractPage(doc.path, pageNumber);
    }

    return doc.path;
  }

  generateDocumentId() {
    return `DOC-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
  }
}

// Usage
const dms = new DocumentManagementSystem('./archives');
const docId = await dms.uploadDocument([
  'contract_page1.png',
  'contract_page2.png',
  'contract_page3.png'
]);
Enter fullscreen mode Exit fullscreen mode

Performance Considerations

// Benchmark different approaches
async function benchmarkConversion(inputPath) {
  console.log('Performance Benchmark:');
  console.log('=====================');

  // Test 1: Uncompressed
  console.time('Uncompressed');
  await sharp(inputPath)
    .tiff({ compression: 'none' })
    .toFile('test_none.tiff');
  console.timeEnd('Uncompressed');

  // Test 2: LZW compression
  console.time('LZW Compression');
  await sharp(inputPath)
    .tiff({ compression: 'lzw' })
    .toFile('test_lzw.tiff');
  console.timeEnd('LZW Compression');

  // Test 3: Deflate compression
  console.time('Deflate Compression');
  await sharp(inputPath)
    .tiff({ compression: 'deflate' })
    .toFile('test_deflate.tiff');
  console.timeEnd('Deflate Compression');

  // File sizes
  const sizes = {
    none: fs.statSync('test_none.tiff').size / 1024,
    lzw: fs.statSync('test_lzw.tiff').size / 1024,
    deflate: fs.statSync('test_deflate.tiff').size / 1024
  };

  console.log('\nFile Sizes:');
  console.log(`Uncompressed: ${sizes.none.toFixed(2)}KB`);
  console.log(`LZW:          ${sizes.lzw.toFixed(2)}KB (${((1 - sizes.lzw/sizes.none) * 100).toFixed(1)}% smaller)`);
  console.log(`Deflate:      ${sizes.deflate.toFixed(2)}KB (${((1 - sizes.deflate/sizes.none) * 100).toFixed(1)}% smaller)`);

  // Cleanup
  ['none', 'lzw', 'deflate'].forEach(type => {
    fs.unlinkSync(`test_${type}.tiff`);
  });
}
Enter fullscreen mode Exit fullscreen mode

Common Issues and Solutions

Issue 1: Huge File Sizes

// Problem: TIFF files are unexpectedly large

// Solution: Use appropriate compression
async function optimizeTiffSize(inputPath, outputPath) {
  const metadata = await sharp(inputPath).metadata();

  // Choose compression based on content
  let compression = 'lzw';  // Default

  if (metadata.hasAlpha) {
    compression = 'deflate';  // Better for transparency
  } else if (metadata.format === 'jpeg') {
    compression = 'jpeg';  // If original was lossy
  }

  await sharp(inputPath)
    .tiff({
      compression,
      quality: 90,
      xres: Math.min(metadata.density || 72, 300)  // Cap at 300 DPI
    })
    .toFile(outputPath);
}
Enter fullscreen mode Exit fullscreen mode

Issue 2: Color Profile Issues

// Problem: Colors look different after conversion

// Solution: Preserve ICC color profile
async function preserveColorProfile(inputPath, outputPath) {
  await sharp(inputPath)
    .withMetadata({
      icc: true  // Preserve ICC color profile
    })
    .tiff({
      compression: 'lzw'
    })
    .toFile(outputPath);
}
Enter fullscreen mode Exit fullscreen mode

Issue 3: Multi-Page TIFF Corruption

# Problem: Multi-page TIFF becomes corrupted

# Solution: Validate each page before combining
def create_validated_multipage_tiff(png_list, output_path):
    validated_images = []

    for i, png_path in enumerate(png_list):
        try:
            img = Image.open(png_path)
            img.verify()  # Check if image is valid
            img = Image.open(png_path)  # Reopen after verify
            validated_images.append(img)
        except Exception as e:
            print(f"Warning: Page {i+1} failed validation: {e}")
            continue

    if not validated_images:
        raise ValueError("No valid images to process")

    validated_images[0].save(
        output_path,
        format='TIFF',
        compression='tiff_lzw',
        save_all=True,
        append_images=validated_images[1:]
    )

    print(f"✓ Created validated {len(validated_images)}-page TIFF")
Enter fullscreen mode Exit fullscreen mode

Testing Your Conversions

// Jest tests for TIFF conversion
const sharp = require('sharp');
const fs = require('fs');

describe('PNG to TIFF Conversion', () => {
  const testPng = 'test-image.png';
  const outputTiff = 'output.tiff';

  afterEach(() => {
    if (fs.existsSync(outputTiff)) {
      fs.unlinkSync(outputTiff);
    }
  });

  test('converts PNG to TIFF successfully', async () => {
    await pngToTiff(testPng, outputTiff);
    expect(fs.existsSync(outputTiff)).toBe(true);
  });

  test('preserves image dimensions', async () => {
    const pngMetadata = await sharp(testPng).metadata();
    await pngToTiff(testPng, outputTiff);
    const tiffMetadata = await sharp(outputTiff).metadata();

    expect(tiffMetadata.width).toBe(pngMetadata.width);
    expect(tiffMetadata.height).toBe(pngMetadata.height);
  });

  test('applies LZW compression', async () => {
    await pngToTiff(testPng, outputTiff, { compression: 'lzw' });
    const metadata = await sharp(outputTiff).metadata();
    expect(metadata.compression).toBe('lzw');
  });

  test('sets correct DPI', async () => {
    await pngToTiff(testPng, outputTiff, { xres: 300, yres: 300 });
    const metadata = await sharp(outputTiff).metadata();
    expect(metadata.density).toBe(300);
  });
});
Enter fullscreen mode Exit fullscreen mode

Conclusion: TIFF for Professional Workflows

While PNG dominates web development, TIFF remains essential for:

Professional printing - CMYK support, high DPI

Medical imaging - 16-bit grayscale, extensive metadata

Document management - Multi-page support, archival quality

GIS applications - Geographic data embedding

Long-term archival - Proven stability, wide compatibility

Quick Decision Guide:

Need multi-page document? → TIFF
Archival/preservation? → TIFF (uncompressed)
Print production? → TIFF (CMYK, 300 DPI)
Web display? → PNG or WebP
Medical imaging? → TIFF (16-bit)
General photos? → JPEG or WebP
Enter fullscreen mode Exit fullscreen mode

Understanding PNG to TIFF conversion opens doors to professional image processing applications beyond typical web development. Whether you're building document management systems, medical software, or print production tools, mastering TIFF is essential.


What professional imaging challenges are you facing? Share your use case in the comments!

webdev #imageprocessing #tiff #documentmanagement #nodejs

Top comments (0)