pdf_to_image_converter

A Flutter package that provides easy-to-use utilities for converting PDF documents to images. Perfect for apps that need to display, edit, or share PDF content as images.

Features

  • PDF File Selection - Built-in file picker for easy PDF selection
  • Page-by-Page Rendering - Convert individual PDF pages to high-quality images
  • Quality Presets - 6 predefined quality levels (thumbnail, low, medium, high, print, ultra)
  • Page Rotation - Render pages at 0°, 90°, 180°, or 270° angles
  • Batch Processing - Render and save all pages at once with progress tracking
  • Page Range Rendering - Convert specific page ranges (e.g., pages 5-10)
  • Thumbnail Generation - Quick low-resolution previews with custom dimensions
  • Custom Per-Page Settings - Different quality/rotation for each page in batch operations
  • Cancellation Support - Stop long-running operations mid-process
  • Page Dimensions - Get size and dimensions for single or all pages
  • PDF Metadata - Extract document info (title, page count, etc.)
  • File Export - Save rendered images to files with custom paths and naming
  • High Performance - Optimized rendering with customizable scale
  • Progress Callbacks - Track rendering and saving progress in real-time
  • Fully Documented - 100% dartdoc coverage for all public APIs

Installation

  1. Add the latest version of package to your pubspec.yaml (and run dart pub get):
dependencies:
  pdf_to_image_converter: ^0.0.5
  1. Import the package and use it in your Flutter App.
import 'package:pdf_to_image_converter/pdf_to_image_converter.dart';

Example

The pdf_to_image_converter package allows you to select a PDF file, render its pages as images, and save them to the gallery.


import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:pdf_to_image_converter/pdf_to_image_converter.dart';
import 'package:path_provider/path_provider.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'PDF to Image Converter',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const PdfToImageScreen(),
    );
  }
}

class PdfToImageScreen extends StatefulWidget {
  const PdfToImageScreen({Key? key}) : super(key: key);

  @override
  State<PdfToImageScreen> createState() => _PdfToImageScreenState();
}

class _PdfToImageScreenState extends State<PdfToImageScreen> {
  final PdfImageConverter _converter = PdfImageConverter();
  Uint8List? _image;

  void _showSnackbar(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(message),
        duration: const Duration(seconds: 2),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('PDF to Image Converter'),
      ),
      body: Center(
        child: SingleChildScrollView(
          child: Column(
            children: <Widget>[
              ElevatedButton(
                child: const Text('Select PDF'),
                onPressed: () async {
                  final path = await PdfPicker.pickPdf();
                  if (path != null) {
                    await _converter.openPdf(path);
                    _image = await _converter.renderPage(0);
                    setState(() {});
                  }
                },
              ),
              if (_converter.isOpen)
                ElevatedButton(
                  child: const Text('Close PDF'),
                  onPressed: () async {
                    await _converter.closePdf();
                    setState(() {});
                  },
                ),
              if (_image != null) ...[
                const Text('Image area:'),
                Image(image: MemoryImage(_image!)),
                ElevatedButton(
                  child: const Text('Save Image'),
                  onPressed: () async {
                    if (_image != null) {
                      final directory = await getApplicationDocumentsDirectory();
                      final filePath = '${directory.path}/page_${_converter.currentPage + 1}.png';
                      
                      await _converter.saveImage(_image!, filePath, (bool isSuccess) {
                        _showSnackbar(
                          isSuccess
                              ? 'Image saved to: $filePath'
                              : 'Failed to save image.',
                        );
                      });
                    }
                  },
                ),
              ],
              if (_converter.isOpen) ...[
                Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    TextButton.icon(
                      onPressed: _converter.currentPage > 0
                          ? () async {
                              _image = await _converter.renderPage(
                                  _converter.currentPage - 1);
                              setState(() {});
                            }
                          : null,
                      icon: const Icon(Icons.chevron_left),
                      label: const Text('Previous'),
                    ),
                    TextButton.icon(
                      onPressed: _converter.currentPage < (_converter.pageCount - 1)
                          ? () async {
                              _image = await _converter.renderPage(
                                  _converter.currentPage + 1);
                              setState(() {});
                            }
                          : null,
                      icon: const Icon(Icons.chevron_right),
                      label: const Text('Next'),
                    ),
                  ],
                ),
              ],
            ],
          ),
        ),
      ),
    );
  }
}


Quick Start

Here's a minimal example to get you started:

import 'dart:io';
import 'package:pdf_to_image_converter/pdf_to_image_converter.dart';
import 'package:path_provider/path_provider.dart';

// Pick a PDF file
final path = await PdfPicker.pickPdf();

if (path != null) {
  // Create converter instance
  final converter = PdfImageConverter();
  
  // Open the PDF
  await converter.openPdf(path);
  
  // Get PDF metadata
  print('Total pages: ${converter.pageCount}');
  print('Title: ${converter.metadata?.title}');
  
  // Get page dimensions
  final size = await converter.getPageSize(0);
  print('Page size: ${size?.width} x ${size?.height}');
  
  // Render with quality preset
  final imageBytes = await converter.renderPage(
    0, 
    quality: RenderQuality.high,
  );
  
  // Get directory to save image
  final directory = await getApplicationDocumentsDirectory();
  final filePath = '${directory.path}/page_1.png';
  
  // Save to file
  await converter.saveImage(imageBytes, filePath, (success) {
    print('Saved: $success');
  });
  
  // Clean up
  await converter.closePdf();
}

Batch Processing with Cancellation

Convert all pages at once with progress tracking and cancellation support:

import 'dart:io';
import 'package:path_provider/path_provider.dart';

final path = await PdfPicker.pickPdf();
if (path != null) {
  final converter = PdfImageConverter();
  await converter.openPdf(path);
  
  // Create cancellation token
  final token = CancellationToken();
  
  // Render all pages with progress and quality preset
  final images = await converter.renderAllPages(
    quality: RenderQuality.medium,
    cancellationToken: token,
    onProgress: (current, total) {
      print('Rendering: $current/$total');
      // Cancel after 5 pages if needed
      // if (current >= 5) token.cancel();
    },
  );
  
  // Get output directory
  final directory = await getApplicationDocumentsDirectory();
  final outputDir = '${directory.path}/pdf_images';
  
  // Save all images with progress
  final saved = await converter.saveAllImages(
    images,
    outputDir,
    fileNamePattern: 'page_{index}.png',
    cancellationToken: token,
    onProgress: (current, total, success) {
      print('Saving: $current/$total - Success: $success');
    },
  );
  
  print('Saved $saved images to $outputDir');
  await converter.closePdf();
}

Usage Examples

1. Using Quality Presets

Quality presets provide optimized settings for different use cases:

// Thumbnail quality (1.0x) - Fast, low quality
final thumbnail = await converter.renderPage(0, quality: RenderQuality.thumbnail);

// Low quality (1.5x) - Quick previews
final preview = await converter.renderPage(0, quality: RenderQuality.low);

// Medium quality (2.0x) - Balanced
final medium = await converter.renderPage(0, quality: RenderQuality.medium);

// High quality (3.0x) - Default, best for viewing
final high = await converter.renderPage(0, quality: RenderQuality.high);

// Print quality (4.0x) - High resolution
final print = await converter.renderPage(0, quality: RenderQuality.print);

// Ultra quality (5.0x) - Maximum quality
final ultra = await converter.renderPage(0, quality: RenderQuality.ultra);

2. Page Rotation

Rotate pages while rendering:

// Normal orientation
final normal = await converter.renderPage(0, rotation: PageRotation.rotate0);

// Rotate 90 degrees clockwise
final rotated90 = await converter.renderPage(0, rotation: PageRotation.rotate90);

// Rotate 180 degrees
final rotated180 = await converter.renderPage(0, rotation: PageRotation.rotate180);

// Rotate 270 degrees clockwise (90 counter-clockwise)
final rotated270 = await converter.renderPage(0, rotation: PageRotation.rotate270);

3. Page Range Rendering

Convert only specific pages:

// Render pages 5-10 (indices 4-9)
final images = await converter.renderPageRange(
  startPage: 4,
  endPage: 9,
  quality: RenderQuality.medium,
  onProgress: (current, total) {
    print('Progress: $current/$total');
  },
);

4. Thumbnail Generation

Quick low-resolution previews:

// Single thumbnail
final thumbnail = await converter.renderThumbnail(
  0,
  maxWidth: 200,
  maxHeight: 300,
);

// All thumbnails
final thumbnails = await converter.renderAllThumbnails(
  maxWidth: 150,
  maxHeight: 200,
  onProgress: (current, total) {
    print('Generated $current/$total thumbnails');
  },
);

5. Custom Settings Per Page

Apply different settings to different pages:

final images = await converter.renderPagesWithConfig([
  PageRenderConfig(
    pageIndex: 0, 
    scale: 2.0,
    background: Colors.white,
  ),
  PageRenderConfig(
    pageIndex: 1, 
    scale: 3.0, 
    rotation: PageRotation.rotate90,
  ),
  PageRenderConfig(
    pageIndex: 2, 
    scale: 1.5,
    background: Colors.grey[200]!,
  ),
]);

6. Cancellation Support

Stop long operations:

final token = CancellationToken();

// Start rendering in background
Future.delayed(Duration.zero, () async {
  final images = await converter.renderAllPages(
    quality: RenderQuality.high,
    cancellationToken: token,
    onProgress: (current, total) {
      print('Rendering: $current/$total');
    },
  );
  print('Completed: ${images.length} pages');
});

// Cancel after 3 seconds
await Future.delayed(Duration(seconds: 3));
token.cancel();
print('Cancelled!');

7. Getting All Page Sizes

Analyze page dimensions before rendering:

final sizes = await converter.getAllPageSizes();
for (int i = 0; i < sizes.length; i++) {
  print('Page $i: ${sizes[i].width} x ${sizes[i].height}');
}

8. Accessing PDF Metadata

Get document information:

await converter.openPdf(path);
final metadata = converter.metadata;
print('Page count: ${metadata?.pageCount}');
print('Title: ${metadata?.title}');

API Documentation

PdfImageConverter

Main class for handling PDF to image conversion.

Properties

  • bool isOpen - Returns true if a PDF is currently open
  • int pageCount - Total number of pages in the open PDF
  • int currentPage - Index of the currently rendered page
  • PdfMetadata? metadata - Metadata of the currently open PDF

Core Methods

  • Future<void> openPdf(String path) - Opens a PDF from file path
  • Future<void> closePdf() - Closes the PDF and releases resources

Rendering Methods

  • Future<Uint8List?> renderPage(int pageIndex, {double? scale, RenderQuality quality, Color background, PageRotation rotation}) - Renders a single page with customizable settings
  • Future<List<Uint8List?>> renderAllPages({double? scale, RenderQuality quality, Color background, PageRotation rotation, CancellationToken? cancellationToken, Function? onProgress}) - Renders all pages with optional cancellation
  • Future<List<Uint8List?>> renderPageRange({required int startPage, required int endPage, double? scale, RenderQuality quality, Color background, PageRotation rotation, CancellationToken? cancellationToken, Function? onProgress}) - Renders a specific range of pages
  • Future<Uint8List?> renderThumbnail(int pageIndex, {double maxWidth, double maxHeight, Color background}) - Generates a thumbnail with max dimensions
  • Future<List<Uint8List?>> renderAllThumbnails({double maxWidth, double maxHeight, Color background, CancellationToken? cancellationToken, Function? onProgress}) - Generates thumbnails for all pages
  • Future<List<Uint8List?>> renderPagesWithConfig(List<PageRenderConfig> pageConfigs, {CancellationToken? cancellationToken, Function? onProgress}) - Renders multiple pages with custom settings per page

Page Information Methods

  • Future<Size?> getPageSize(int pageIndex) - Gets the dimensions of a specific page
  • Future<List<Size>> getAllPageSizes() - Gets dimensions of all pages

Saving Methods

  • Future<void> saveImage(Uint8List imageBytes, String filePath, Function(bool) onSave) - Saves a single image to file
  • Future<int> saveAllImages(List<Uint8List?> imagesList, String outputDirectory, {String fileNamePattern, CancellationToken? cancellationToken, Function? onProgress}) - Saves multiple images with custom naming

RenderQuality Enum

Predefined quality presets:

  • RenderQuality.thumbnail - 1.0x scale
  • RenderQuality.low - 1.5x scale
  • RenderQuality.medium - 2.0x scale
  • RenderQuality.high - 3.0x scale (default)
  • RenderQuality.print - 4.0x scale
  • RenderQuality.ultra - 5.0x scale

PageRotation Enum

Rotation angles:

  • PageRotation.rotate0 - No rotation (0°)
  • PageRotation.rotate90 - 90° clockwise
  • PageRotation.rotate180 - 180° rotation
  • PageRotation.rotate270 - 270° clockwise (90° counter-clockwise)

PageRenderConfig Class

Configuration for rendering individual pages:

PageRenderConfig({
  required int pageIndex,
  double scale = 3.0,
  Color background = Colors.white,
  PageRotation rotation = PageRotation.rotate0,
})

CancellationToken Class

Token for cancelling operations:

  • bool isCancelled - Check if cancellation was requested
  • void cancel() - Request cancellation
  • void reset() - Reset the token state

PdfMetadata Class

Contains PDF document metadata:

  • int pageCount - Total number of pages
  • String? title - Document title
  • String? author - Document author
  • String? subject - Document subject
  • String? creator - Creating application
  • String? producer - PDF producer
  • DateTime? creationDate - Creation date
  • DateTime? modificationDate - Last modification date

PdfPicker

Utility class for PDF file selection.

Methods

  • static Future<String?> pickPdf() - Opens file picker for PDF selection

Platform Configuration

Android

Add file access permissions to android/app/src/main/AndroidManifest.xml:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

For Android 13+ (API 33+), also add:

<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>

iOS

Add file picker permission to ios/Runner/Info.plist:

<key>NSPhotoLibraryUsageDescription</key>
<string>We need access to select PDF files</string>

Note on Saving Images

This package saves images to files on the device. To save to the gallery, use additional packages like gallery_saver or image_gallery_saver in combination with this package.

Advanced Examples

Complete Workflow with All Features

import 'package:pdf_to_image_converter/pdf_to_image_converter.dart';
import 'package:path_provider/path_provider.dart';

Future<void> advancedPdfConversion() async {
  // 1. Pick PDF
  final path = await PdfPicker.pickPdf();
  if (path == null) return;
  
  final converter = PdfImageConverter();
  
  // 2. Open and inspect
  await converter.openPdf(path);
  print('Pages: ${converter.pageCount}');
  print('Title: ${converter.metadata?.title}');
  
  // 3. Get all page sizes
  final sizes = await converter.getAllPageSizes();
  print('First page: ${sizes[0].width} x ${sizes[0].height}');
  
  // 4. Generate thumbnails for preview
  final thumbnails = await converter.renderAllThumbnails(
    maxWidth: 150,
    maxHeight: 200,
    onProgress: (current, total) {
      print('Thumbnail: $current/$total');
    },
  );
  
  // 5. Render specific pages with different settings
  final token = CancellationToken();
  final images = await converter.renderPagesWithConfig([
    PageRenderConfig(pageIndex: 0, scale: 4.0), // High quality cover
    PageRenderConfig(pageIndex: 1, scale: 2.0), // Medium quality
    PageRenderConfig(
      pageIndex: 2, 
      scale: 2.0, 
      rotation: PageRotation.rotate90,
    ), // Rotated
  ], cancellationToken: token);
  
  // 6. Render page range with rotation
  final pages5to10 = await converter.renderPageRange(
    startPage: 4,
    endPage: 9,
    quality: RenderQuality.medium,
    rotation: PageRotation.rotate0,
  );
  
  // 7. Save with custom naming
  final directory = await getApplicationDocumentsDirectory();
  final saved = await converter.saveAllImages(
    images,
    '${directory.path}/pdf_output',
    fileNamePattern: 'document_page_{index}.png',
    onProgress: (current, total, success) {
      print('Saved: $current/$total - ${success ? "Success" : "Failed"}');
    },
  );
  
  print('Total saved: $saved images');
  
  // 8. Clean up
  await converter.closePdf();
}

Performance Optimization

Choose the right quality for your use case:

// For preview/thumbnails - Fast rendering
final preview = await converter.renderPage(0, quality: RenderQuality.thumbnail);

// For in-app display - Balanced
final display = await converter.renderPage(0, quality: RenderQuality.medium);

// For export/sharing - High quality
final export = await converter.renderPage(0, quality: RenderQuality.print);

// For archival/professional use - Maximum quality
final archival = await converter.renderPage(0, quality: RenderQuality.ultra);

Example App

For a complete working example, check the example directory. It demonstrates:

  • PDF file selection with PdfPicker
  • Quality presets selection
  • Page rotation controls
  • Page rendering with custom quality
  • Page navigation with page counter
  • Page range rendering
  • Thumbnail generation
  • Batch rendering and saving all pages
  • Cancellation of operations
  • Progress tracking for batch operations
  • Image preview and display
  • Metadata display
  • Status updates and loading indicators

Run the example:

cd example
flutter pub get
flutter run

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Issues and Feedback

Please file issues, bugs, or feature requests in our issue tracker.

Performance Tips

  1. Choose appropriate quality: Use lower quality presets for previews and higher for final output
  2. Use page ranges: Instead of rendering all pages, render only what's needed
  3. Generate thumbnails first: Show quick previews before full rendering
  4. Use cancellation: Allow users to stop long operations
  5. Batch operations wisely: Break large PDFs into smaller chunks
  6. Memory management: Always call closePdf() when done

Troubleshooting

Issue: Out of memory with large PDFs

  • Solution: Use lower quality presets or render pages in smaller batches

Issue: Rendering is slow

  • Solution: Use RenderQuality.thumbnail or RenderQuality.low for previews, then render at higher quality only when needed

Issue: Images are blurry

  • Solution: Increase quality preset (e.g., RenderQuality.print or RenderQuality.ultra)

Issue: Need to stop a long operation

  • Solution: Use CancellationToken with rendering and saving methods

Changelog

Latest Version

  • Added quality presets (thumbnail, low, medium, high, print, ultra)
  • Added page rotation support (0°, 90°, 180°, 270°)
  • Added page range rendering
  • Added thumbnail generation methods
  • Added cancellation support with CancellationToken
  • Added custom per-page rendering configurations
  • Added PDF metadata extraction
  • Added getAllPageSizes method
  • Enhanced documentation with comprehensive examples

Next Goals

  • Add support for text extraction from PDFs
  • Add watermark support
  • Add image compression options
  • Add support for encrypted PDFs
  • Add batch conversion from multiple PDFs