# Vue HEIC Image

**vue-heic-image** helps Vue 3 apps show **Apple HEIC/HEIF** photos (iPhone, iPad) in the browser and convert them to **PNG**, **JPEG**, or **GIF** for uploads and sharing. It uses **heic2any** under the hood, exposes a **`HeicImage`** component and **`useHeicImage`** composable, and ships **TypeScript** types—ideal for **Vite**, **Nuxt** (client-only conversion), and any **SPA** that needs HEIC support without a server-side converter.

## Live Demo

Check out the live demo: [Vue HEIC Image Demo](https://vue-heic-image.netlify.app)

[![Netlify Status](https://api.netlify.com/api/v1/badges/8666b46e-9d5f-4819-8c9e-e37583cc77dc/deploy-status)](https://app.netlify.com/sites/vue-heic-image/deploys)

## Features

- Display HEIC images directly in the browser (with optional native display on Safari via `preferNativeHeic`)
- Convert HEIC to PNG, JPEG, or GIF (powered by [heic2any](https://github.com/alexcorvi/heic2any), loaded on demand)
- Smart detection of HEIC files (no conversion for standard images)
- Multi-frame HEIC: show all converted frames with the default layout or a custom `#frames` slot
- Component events (`converted`, `load`, `error`) and common `<img>` passthrough props (`loading`, `objectFit`, etc.)
- Helpers: `downloadBlob`, `blobToFile`
- Quality control for JPEG output and GIF frame interval
- TypeScript support
- Use as a component or composable

## Installation

```bash
npm install vue-heic-image
# or
yarn add vue-heic-image
# or
pnpm add vue-heic-image
```

## Usage

### Basic Component Usage

```vue
<template>
  <HeicImage src="path/to/image.heic" alt="My Image" />
</template>

<script setup>
import { HeicImage } from 'vue-heic-image';
</script>
```

### Advanced Component Usage

```vue
<template>
  <HeicImage
    :src="imageFile"
    alt="My HEIC Image"
    :toType="'image/jpeg'"
    :quality="0.8"
    :multiple="true"
    :preferNativeHeic="true"
    object-fit="cover"
    class="my-image"
    @converted="onConverted"
    @load="onLoad"
    @error="onError"
  >
    <template #loading="{ isHeic }">
      <div class="loading">
        {{ isHeic ? 'Converting HEIC...' : 'Loading image...' }}
      </div>
    </template>

    <template #error="{ error }">
      <div class="error">
        Failed to load: {{ error.message }}
      </div>
    </template>
  </HeicImage>
</template>

<script setup lang="ts">
import type { HeicConvertedPayload } from 'vue-heic-image';

function onConverted(p: HeicConvertedPayload) {
  console.log(p.blobs, p.isHeic, p.usedNative);
}
function onLoad() {
  console.log('All default frame images finished loading');
}
function onError(e: Error | Event) {
  console.error(e);
}
</script>
```

### Multi-frame slot

When `:multiple="true"` and the converter returns more than one image, you can customize layout with the `frames` slot. If you use this slot, you render the images yourself; the component does not emit `load` (handle loading in your own UI).

```vue
<template>
  <HeicImage :src="file" :multiple="true" alt="Frames">
    <template #frames="{ urls, blobs, count }">
      <div class="grid">
        <img v-for="(url, i) in urls" :key="i" :src="url" :alt="`Frame ${i + 1}`" />
      </div>
    </template>
  </HeicImage>
</template>
```

### File Input Example

```vue
<template>
  <div>
    <input type="file" accept="image/*,.heic" @change="handleFileSelect" />

    <div v-if="selectedFile">
      <HeicImage
        :src="selectedFile"
        :toType="outputFormat"
        :quality="quality"
        class="preview-image"
      >
        <template #loading="{ isHeic }">
          <div class="status">
            {{ isHeic ? 'Converting HEIC...' : 'Loading...' }}
          </div>
        </template>
      </HeicImage>

      <div class="controls">
        <select v-model="outputFormat">
          <option value="image/png">PNG</option>
          <option value="image/jpeg">JPEG</option>
          <option value="image/gif">GIF</option>
        </select>

        <input
          v-if="outputFormat === 'image/jpeg'"
          type="range"
          v-model="quality"
          min="0"
          max="1"
          step="0.1"
        />
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { HeicImage } from 'vue-heic-image';
import type { HeicOutputType } from 'vue-heic-image';

const selectedFile = ref<File | null>(null);
const outputFormat = ref<HeicOutputType>('image/png');
const quality = ref(0.92);

const handleFileSelect = (event: Event) => {
  const file = (event.target as HTMLInputElement).files?.[0];
  if (file) selectedFile.value = file;
};
</script>
```

### Using the Composable

```typescript
import { useHeicImage } from 'vue-heic-image';

const { convertHeicToImage, isLoading, error, isHeic, usedNative } = useHeicImage({
  toType: 'image/png',
  quality: 0.92,
  multiple: false,
  gifInterval: 0.4,
  preferNativeHeic: false,
});

const handleFile = async (file: File) => {
  try {
    const result = await convertHeicToImage(file);

    if (isHeic.value) {
      console.log('HEIC processed; usedNative:', usedNative.value);
    }

    const url = URL.createObjectURL(Array.isArray(result) ? result[0] : result);
    return () => URL.revokeObjectURL(url);
  } catch (e) {
    console.error('Processing failed:', e);
  }
};
```

### Helpers

```typescript
import { downloadBlob, blobToFile } from 'vue-heic-image';

const file = blobToFile(blob, 'photo.png', 'image/png');
downloadBlob(file, file.name);
```

### Nuxt and SSR

Conversion uses browser APIs and a dynamically imported `heic2any`. Do not run `convertHeicToImage` on the server. Wrap previews in `ClientOnly` (or equivalent) and call the composable only in client-side code:

```vue
<template>
  <ClientOnly>
    <HeicImage :src="file" alt="Preview" />
  </ClientOnly>
</template>
```

## Props and options

### Component props

| Name | Type | Default | Description |
|------|------|---------|-------------|
| src | `File \| Blob \| string` | - | Source image (HEIC or standard formats) |
| alt | `string` | - | Alternative text for the image |
| toType | `'image/png' \| 'image/gif' \| 'image/jpeg'` | `'image/png'` | Output format for HEIC conversion |
| quality | `number` | `0.92` | JPEG quality (0–1), only for `'image/jpeg'` |
| multiple | `boolean` | `false` | Output multiple images for multi-frame HEIC |
| gifInterval | `number` | `0.4` | Frame interval for GIF output (seconds) |
| preferNativeHeic | `boolean` | `false` | Try Safari native HEIC display when `toType` is PNG and `multiple` is false; falls back to conversion |
| width | `number \| string` | - | Image width |
| height | `number \| string` | - | Image height |
| class | `string` | - | CSS class names on `<img>` |
| objectFit | CSS `object-fit` | - | Applied as inline style on `<img>` |
| loading, decoding, crossorigin, referrerpolicy, sizes, srcset, fetchpriority, draggable | see `HeicImageImgAttrs` | - | Forwarded to `<img>` |

### Events

| Event | Payload | Description |
|-------|---------|-------------|
| converted | `{ blobs, isHeic, usedNative }` | After a successful resolve (single blob or array) |
| load | - | After all default-rendered frame images fire `load` (not used when you override the `frames` slot) |
| error | `Error \| Event` | Composable conversion error or `<img>` error |

### Slot props

**Loading**

```vue
<template #loading="{ isHeic }">
  <!-- isHeic: true when the source is treated as HEIC -->
</template>
```

**Error**

```vue
<template #error="{ error }">
  <!-- error: Error -->
</template>
```

**Frames** (optional; when using this slot you control the DOM and `load` is not emitted by the component)

```vue
<template #frames="{ urls, blobs, count }">
  <!-- urls: string[] object URLs; blobs: Blob[]; count: number -->
</template>
```

## Smart detection

The package detects HEIC images through MIME type and, when needed, file signature analysis. Non-HEIC images are passed through without calling `heic2any`.

## Browser support

- Modern browsers (Chrome, Firefox, Safari, Edge)
- Requires `fetch` (for URL sources), `URL.createObjectURL`, and `Image` for optional native HEIC probing
- iOS Safari 13.4+
- Chrome/Edge 89+
- Firefox 86+

## License

MIT
