@Override
    public void writeImage(final BufferedImage src, final OutputStream os, GifImagingParameters params) throws ImagingException, IOException {
        if (params == null) {
            params = new GifImagingParameters();
        }

        final String xmpXml = params.getXmpXml();

        final int width = src.getWidth();
        final int height = src.getHeight();

        final boolean hasAlpha = new PaletteFactory().hasTransparency(src);

        final int maxColors = hasAlpha ? 255 : 256;

        Palette palette2 = new PaletteFactory().makeExactRgbPaletteSimple(src, maxColors);
        // int[] palette = new PaletteFactory().makePaletteSimple(src, 256);
        // Map palette_map = paletteToMap(palette);

        if (palette2 == null) {
            palette2 = new PaletteFactory().makeQuantizedRgbPalette(src, maxColors);
            if (LOGGER.isLoggable(Level.FINE)) {
                LOGGER.fine("quantizing");
            }
        } else if (LOGGER.isLoggable(Level.FINE)) {
            LOGGER.fine("exact palette");
        }

        if (palette2 == null) {
            throw new ImagingException("Gif: can't write images with more than 256 colors");
        }
        final int paletteSize = palette2.length() + (hasAlpha ? 1 : 0);

        try (AbstractBinaryOutputStream bos = AbstractBinaryOutputStream.littleEndian(os)) {

            // write Header
            os.write(0x47); // G magic numbers
            os.write(0x49); // I
            os.write(0x46); // F

            os.write(0x38); // 8 version magic numbers
            os.write(0x39); // 9
            os.write(0x61); // a

            // Logical Screen Descriptor.

            bos.write2Bytes(width);
            bos.write2Bytes(height);

            final int colorTableScaleLessOne = paletteSize > 128 ? 7
                    : paletteSize > 64 ? 6 : paletteSize > 32 ? 5 : paletteSize > 16 ? 4 : paletteSize > 8 ? 3 : paletteSize > 4 ? 2 : paletteSize > 2 ? 1 : 0;

            final int colorTableSizeInFormat = 1 << colorTableScaleLessOne + 1;
            {
                final byte colorResolution = (byte) colorTableScaleLessOne; // TODO:
                final int packedFields = (7 & colorResolution) * 16;
                bos.write(packedFields); // one byte
            }
            {
                final byte backgroundColorIndex = 0;
                bos.write(backgroundColorIndex);
            }
            {
                final byte pixelAspectRatio = 0;
                bos.write(pixelAspectRatio);
            }

            // {
            // write Global Color Table.

            // }

            { // ALWAYS write GraphicControlExtension
                bos.write(EXTENSION_CODE);
                bos.write((byte) 0xf9);
                // bos.write(0xff & (kGraphicControlExtension >> 8));
                // bos.write(0xff & (kGraphicControlExtension >> 0));

                bos.write((byte) 4); // block size;
                final int packedFields = hasAlpha ? 1 : 0; // transparency flag
                bos.write((byte) packedFields);
                bos.write((byte) 0); // Delay Time
                bos.write((byte) 0); // Delay Time
                bos.write((byte) (hasAlpha ? palette2.length() : 0)); // Transparent
                // Color
                // Index
                bos.write((byte) 0); // terminator
            }

            if (null != xmpXml) {
                bos.write(EXTENSION_CODE);
                bos.write(APPLICATION_EXTENSION_LABEL);

                bos.write(XMP_APPLICATION_ID_AND_AUTH_CODE.length); // 0x0B
                bos.write(XMP_APPLICATION_ID_AND_AUTH_CODE);

                final byte[] xmpXmlBytes = xmpXml.getBytes(StandardCharsets.UTF_8);
                bos.write(xmpXmlBytes);

                // write "magic trailer"
                for (int magic = 0; magic <= 0xff; magic++) {
                    bos.write(0xff - magic);
                }

                bos.write((byte) 0); // terminator

            }

            { // Image Descriptor.
                bos.write(IMAGE_SEPARATOR);
                bos.write2Bytes(0); // Image Left Position
                bos.write2Bytes(0); // Image Top Position
                bos.write2Bytes(width); // Image Width
                bos.write2Bytes(height); // Image Height

                {
                    final boolean localColorTableFlag = true;
                    // boolean LocalColorTableFlag = false;
                    final boolean interlaceFlag = false;
                    final boolean sortFlag = false;
                    final int sizeOfLocalColorTable = colorTableScaleLessOne;

                    // int SizeOfLocalColorTable = 0;

                    final int packedFields;
                    if (localColorTableFlag) {
                        packedFields = LOCAL_COLOR_TABLE_FLAG_MASK | (interlaceFlag ? INTERLACE_FLAG_MASK : 0) | (sortFlag ? SORT_FLAG_MASK : 0)
                                | 7 & sizeOfLocalColorTable;
                    } else {
                        packedFields = 0 | (interlaceFlag ? INTERLACE_FLAG_MASK : 0) | (sortFlag ? SORT_FLAG_MASK : 0) | 7 & sizeOfLocalColorTable;
                    }
                    bos.write(packedFields); // one byte
                }
            }

            { // write Local Color Table.
                for (int i = 0; i < colorTableSizeInFormat; i++) {
                    if (i < palette2.length()) {
                        final int rgb = palette2.getEntry(i);

                        final int red = 0xff & rgb >> 16;
                        final int green = 0xff & rgb >> 8;
                        final int blue = 0xff & rgb >> 0;

                        bos.write(red);
                        bos.write(green);
                        bos.write(blue);
                    } else {
                        bos.write(0);
                        bos.write(0);
                        bos.write(0);
                    }
                }
            }

            { // get Image Data.
//            int image_data_total = 0;

                int lzwMinimumCodeSize = colorTableScaleLessOne + 1;
                // LZWMinimumCodeSize = Math.max(8, LZWMinimumCodeSize);
                if (lzwMinimumCodeSize < 2) {
                    lzwMinimumCodeSize = 2;
                }

                // TODO:
                // make
                // better
                // choice
                // here.
                bos.write(lzwMinimumCodeSize);

                final MyLzwCompressor compressor = new MyLzwCompressor(lzwMinimumCodeSize, ByteOrder.LITTLE_ENDIAN, false); // GIF
                // Mode);

                final byte[] imageData = Allocator.byteArray(width * height);
                for (int y = 0; y < height; y++) {
                    for (int x = 0; x < width; x++) {
                        final int argb = src.getRGB(x, y);
                        final int rgb = 0xffffff & argb;
                        final int index;

                        if (hasAlpha) {
                            final int alpha = 0xff & argb >> 24;
                            final int alphaThreshold = 255;
                            if (alpha < alphaThreshold) {
                                index = palette2.length(); // is transparent
                            } else {
                                index = palette2.getPaletteIndex(rgb);
                            }
                        } else {
                            index = palette2.getPaletteIndex(rgb);
                        }

                        imageData[y * width + x] = (byte) index;
                    }
                }

                final byte[] compressed = compressor.compress(imageData);
                writeAsSubBlocks(compressed, bos);
//            image_data_total += compressed.length;
            }

            // palette2.dump();

            bos.write(TERMINATOR_BYTE);

        }
        os.close();
    }
Extracts embedded XML metadata as XML string.
<p>

@param byteSource File containing image data.
@param params     Map of optional parameters, defined in ImagingConstants.
@return Xmp Xml as String, if present. Otherwise, returns null.
src/main/java/org/apache/commons/imaging/formats/gif/GifImageParser.java