Appwrite Image Previews Receive Massive New Updates

Hey everyone, we have just pushed a massive update to the image preview API with few new image manipulation functionalities. Along with existing features like cropping, converting the output format, and adjustable image quality, we now support border (borderWidth and borderColor parameters), border radius (borderRadius parameter), opacity, and rotation parameters to manipulate images.

In this tutorial, we will learn how we can leverage those features to manipulate images to our liking without using any external services. So let us get started.

Setting up the project

Before you continue with tutorial, make sure you have a hosted version of Appwrite, either locally or on a cloud. If not please go through our docs to install a local instance of Appwrite. You only need Docker to install and run Appwrite.

Once you have the project set up, you can upload few images to storage from the Appwrite console. Then we can use those image file IDs in the following examples to preview the result.

You can use any of our client SDKs or even the REST API directly to make a request to the Preview endpoint. In this tutorial we will be using both our Flutter and Web SDKs to make a request to preview images.

Example requests

So, in order to use the newly introduced image capabilities, you can make a request using the Appwrite's storage service to the file preview endpoint. A request will look like following.

Flutter

Storage storage = Storage(client);
final res = await storage.getFilePreview(
    fileId: fileId, //id of an existing file in the Appwrite Storage
    width: 600, // crop image to the width
    borderWidth: 5, //border width in pixels
    borderColor: 'ff0000', //hex color value without #
    borderRadius: 50, //border radius in pixels
    opacity: 0.8, //opacity between 0-1
    rotation: 45, //rotation between 0-360
    background: 'ff0000', // hex color value without #
    output: 'png', //output image format
)

The Above request will return a Future<Response> where the response is provided by dio package. And the res.data will consist of the Unit8List of bytes of the image, that you can preview in Flutter using Image.memory(res.data).

Web

var appwrite = new Appwrite();
//appwrite.storage.getFilePreview(fileId, width, height, quality, borderWidth, borderColor, borderRadius, opacity, rotation, background, output)
final url = appwrite.storage.getFilePreview(fileId, 800, 0, 100, 5, 'ff0000', 50, 0.8, 45, 'ff0000', 'png');

The above request will return a URL, which can be used as a src attribute for an <img> in order to display the preview image.

A Complete Example with Flutter SDK

Below is a complete example, where you can change different values to see the preview.
I am using fast_color_picker to quickly pick border and background colors. Complete source code is available in GitHub repository

Here is how it looks:

import 'package:appwrite/appwrite.dart';
import 'package:fast_color_picker/fast_color_picker.dart';
import 'package:flutter/material.dart';

const String fileId = "606ec6f150dc6"; //file id of the file from the storage
final String endPoint = "<https://localhost/v1>" // your endpoint
const String project = "606eba7792442"; // your project id

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

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Appwrite Image API',
      theme: ThemeData(
        primarySwatch: Colors.pink,
      ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  double borderRadius = 0;
  double borderWidth = 0;
  Color borderColor = Colors.red;
  Color backgroundColor = Colors.white;
  double opacity = 1;
  Client client = Client();
  Storage storage;

  
  void initState() {
    super.initState();
    client
        .setEndpoint(endPoint)
        .setProject(project)
        .setSelfSigned(); //use self signed only in development
    storage = Storage(client);
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Image API features"),
      ),
      body: ListView(
        children: [
          FutureBuilder(
            future: storage.getFilePreview(
              fileId: fileId,
              width: 600,
              borderWidth: borderWidth.toInt(),
              borderColor: borderColor != null
                  ? "${borderColor.value.toRadixString(16).substring(2)}"
                  : null,
              borderRadius: borderRadius.toInt(),
              opacity: opacity,
              background: backgroundColor != null
                  ? "${backgroundColor.value.toRadixString(16).substring(2)}"
                  : null,
              output: 'png',
            ),
            builder: (context, snapshot) {
              if (snapshot.hasData)
                return Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Image.memory(
                    snapshot.data.data,
                    fit: BoxFit.contain,
                  ),
                );
              return CircularProgressIndicator();
            },
          ),
          Row(
            children: [
              const SizedBox(width: 10.0),
              Text("BR"),
              Expanded(
                child: Slider(
                  label: "$borderRadius",
                  value: borderRadius,
                  min: 0,
                  max: 200,
                  divisions: 10,
                  onChanged: (val) {
                    setState(() {
                      borderRadius = val;
                    });
                  },
                ),
              ),
            ],
          ),
          Row(
            children: [
              const SizedBox(width: 10.0),
              Text("BW"),
              Expanded(
                child: Slider(
                  label: "$borderWidth",
                  value: borderWidth,
                  min: 0,
                  max: 10,
                  divisions: 5,
                  onChanged: (val) {
                    setState(() {
                      borderWidth = val;
                    });
                  },
                ),
              ),
            ],
          ),
          Row(
            children: [
              const SizedBox(width: 10.0),
              Text("OP"),
              Expanded(
                child: Slider(
                  label: "$opacity",
                  value: opacity,
                  min: 0,
                  max: 1,
                  divisions: 5,
                  onChanged: (val) {
                    setState(() {
                      opacity = val;
                    });
                  },
                ),
              ),
            ],
          ),
          Row(
            children: [
              const SizedBox(width: 10.0),
              Text("Border"),
              const SizedBox(width: 10.0),
              Expanded(
                child: FastColorPicker(
                  onColorSelected: (color) {
                    setState(() {
                      borderColor = color;
                    });
                  },
                  selectedColor: borderColor,
                ),
              ),
            ],
          ),
          Row(
            children: [
              const SizedBox(width: 10.0),
              Text("Background"),
              const SizedBox(width: 10.0),
              Expanded(
                child: FastColorPicker(
                  onColorSelected: (color) {
                    setState(() {
                      backgroundColor = color;
                    });
                  },
                  selectedColor: backgroundColor,
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

A Complete Example with Web SDK

Below is a simple example where we can change different values and see the preview image change. I am using web SDK from CDN. Complete source code can be found in GitHub repository.

Here is how it looks

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <img id="image" src="" />
    <form>
        <label for="opacity">Opacity</label>
        <input type="range" id="opacity" name="opacity" min="0" max="1" value="1" step="0.1">
        <br>

        <label for="border-radius">Border Radius</label>
        <input type="range" id="border-radius" name="borderRadius" min="0" max="500" value="90" step="10">
        <br>

        <label for="border-width">Border Width</label>
        <input type="range" id="border-width" name="borderWidth" min="0" max="30" value="5" step="10">
        <br>

        <label for="border-color">Border Color</label>
        <input type="color" id="border-color" name="borderColor" value="#ffffff">
        <br>

        <label for="background">Background</label>
        <input type="color" id="background" name="background" value="#ffffff">
        <br>
    </form>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
    <script>
        var appwrite = new window.Appwrite.Appwrite();
        appwrite
            .setEndpoint('https://demo.appwrite.io/v1')
            .setProject('607fce4176685');

        var fileId = "60b8c432cf0f6";
        var image = document.getElementById('image');
        var form = document.querySelector('form');
        var url = appwrite.storage.getFilePreview(fileId, 800, 0, 100, 0, '', 0, 1, 0, '', 'png');
        form.addEventListener('change', e => {
            var data = Object.fromEntries(new FormData(form));
            for (const key in data) {
                data[key] = data[key].replace('#', '');
            }
            image.src = appwrite.storage.getFilePreview(
                fileId, 
                800, 
                0, 
                100, 
                data.borderWidth, 
                data.borderColor, 
                data.borderRadius, 
                data.opacity, 
                0, 
                data.background);
        });
    </script>
</body>

</html>

References

Learn More

41