/// <summary>
        /// Create the VD frame data from the image
        /// </summary>
        /// <param name="writer">active binary writer</param>
        /// <param name="colors">animation colors list (only required for EC animations)</param>
        /// <param name="centerX">tile center X</param>
        /// <param name="centerY">tile center X</param>
        /// <param name="frameImage">Frame image (with the animation size)</param>
        /// <param name="topX">top X coordinate from the animation frame</param>
        /// <param name="topY">top Y coordinate from the animation frame</param>
        public void ExportVDImageData(BinaryWriter writer, DirectBitmap frameImage, short centerX, short centerY, int topX, int topY, List <ColourEntry> colors = null)
        {
            // write the image initial X coordinate
            writer.Write(centerX);

            // write the image initial Y coordinate
            writer.Write(centerY);

            // write the image width
            writer.Write((ushort)(m_originalVD ? m_width : frameImage.Width));

            // write the image height
            writer.Write((ushort)(m_originalVD ? m_height : frameImage.Height));

            // parse the image line by line
            for (int y = 0; y < m_height; y++)
            {
                // index used to search the first used (NON transparent) pixel in the line
                int i = 0;

                // current position in the line
                int x = 0;

                // we keep cycling until the whole line hans been loaded
                while (i < m_width)
                {
                    // scan all the pixels in the line in search of the first one NON transparent
                    for (i = x; i < m_width; i++)
                    {
                        // did we find the first color?
                        if (m_VDImageData[i, y] != null)
                        {
                            break;
                        }
                    }

                    // did we reach the end of the line?
                    if (i >= m_width)
                    {
                        continue;
                    }

                    // index of the first unused (transparent) pixel in the line AFTER the first colored pixel
                    int j;

                    // now we search the last color of this segment to determine the length
                    for (j = i + 1; j < m_width; j++)
                    {
                        // did we find the last color?
                        if (m_VDImageData[j, y] == null)
                        {
                            break;
                        }
                    }

                    // calculate the size of the segment
                    int length = j - i;

                    // calculate the offset x for the point to start drawing the segment
                    int xOffset = ((j - length - centerX) + (m_originalVD ? 0 : topX)) + 0x200;

                    // calculate the offset y for the point to start drawing the segment
                    int yOffset = ((y - centerY - (m_originalVD ? m_height : frameImage.Height)) + (m_originalVD ? 0 : topY)) + 0x200;

                    // create the data array for this segment
                    byte[] data = new byte[length];

                    // scan the segment to store the index in the colors list
                    for (int r = 0; r < length; r++)
                    {
                        // is this frame from a VD file?
                        if (m_originalVD)
                        {
                            // get the color index from the array
                            string stringColor = m_VDImageData[r + i, y];

                            // get the index for the color in the colors list
                            data[r] = (byte)int.Parse(stringColor);
                        }
                        else // for EC animations we have to search for the color index
                        {
                            data[r] = (byte)GetPaletteIndex(frameImage.GetPixel(r + i + topX, y + topY), colors);;
                        }
                    }

                    // create the header (in bytes)
                    int header = length | (yOffset << 12) | (xOffset << 22);

                    // fix the header
                    header ^= _doubleXor;

                    // write the header
                    writer.Write(header);

                    // write each data byte of the frame
                    foreach (byte b in data)
                    {
                        writer.Write(b);
                    }

                    // move to the next segment
                    x = j + 1;
                    i = x;

                    // allow the form to update before we move to the next
                    Application.DoEvents();
                }
            }

            // write the end of frame flag (header)
            writer.Write(0x7FFF7FFF);
        }
        // --------------------------------------------------------------
        #region PUBLIC FUNCTIONS
        // --------------------------------------------------------------

        /// <summary>
        /// Load the frame image
        /// </summary>
        /// <param name="_ImageData">Byte array to read</param>
        /// <param name="_ImageDataOffset">Data offset</param>
        /// <param name="m_Colours">Colors table to use</param>
        public void LoadFrameImage(byte[] _ImageData, long _ImageDataOffset, List <ColourEntry> m_Colours)
        {
            // create the VD image data array (in case we want to save the image in VD later)
            m_VDImageData = new string[m_width, m_height];

            // create the basic image
            m_image = new DirectBitmap(m_width, m_height);

            // get the current byte index
            int currByte = (int)((long)DataOffset - _ImageDataOffset);

            // starting x and y coordinates for drawing pixels
            int curx = 0;
            int cury = 0;

            // are we still within the image boundries?
            while (cury < m_height)
            {
                // get the next byte
                byte curr = _ImageData[currByte++];

                // are we positioned before the header?
                if (curr < (byte)128)
                {
                    // move to the correct starting point
                    for ( ; curr > (byte)0; curr--)
                    {
                        NextCoordinate(ref curx, ref cury, m_width, m_height);
                    }
                }
                else // correct position
                {
                    // get the next byte
                    byte next = _ImageData[currByte++];

                    // calculate the factors
                    int factor1 = (int)next / 16;
                    int factor2 = (int)next % 16;

                    // is the factor greater than 0?
                    if (factor1 > 0)
                    {
                        // combine the colors
                        Color color = CombineColors(m_Colours[(int)_ImageData[currByte++]].Pixel, m_image.GetPixel(curx, cury), factor1);

                        // color the pixel
                        m_image.SetPixel(curx, cury, color);

                        // flag that this pixel is used (for VD save)
                        m_VDImageData[curx, cury] = "1";

                        // get the next coordinate in the image
                        NextCoordinate(ref curx, ref cury, m_width, m_height);
                    }

                    // scan the other bytes
                    for (byte i = (byte)((uint)curr - 128U); i > (byte)0; i--)
                    {
                        // get the pixel color
                        Color pixel = m_Colours[(int)_ImageData[currByte++]].Pixel;

                        // set the color to the image
                        m_image.SetPixel(curx, cury, pixel);

                        // flag that this pixel is used (for VD save)
                        m_VDImageData[curx, cury] = "1";

                        // move to the next coordinate in the image
                        NextCoordinate(ref curx, ref cury, m_width, m_height);
                    }

                    // is the second factor greater than 0?
                    if (factor2 > 0)
                    {
                        // combine the colors
                        Color color = CombineColors(m_Colours[(int)_ImageData[currByte++]].Pixel, m_image.GetPixel(curx, cury), factor2);

                        // color the pixel
                        m_image.SetPixel(curx, cury, color);

                        // flag that this pixel is used (for VD save)
                        m_VDImageData[curx, cury] = "1";

                        // get the next coordinate in the image
                        NextCoordinate(ref curx, ref cury, m_width, m_height);
                    }
                }
            }
        }
Beispiel #3
0
        // --------------------------------------------------------------
        #region PUBLIC FUNCTIONS
        // --------------------------------------------------------------

        /// <summary>
        /// Create the image for the multi item
        /// </summary>
        /// <param name="gamePath">Game path (used to load the images)</param>
        /// <param name="itemsCache">Reference to the list with all the items</param>
        /// <param name="minZ">Minimum Z to show</param>
        /// <param name="maxZ">Maximum Z to show</param>
        /// <returns>Multi item image</returns>
        public Bitmap GetImage(string gamePath, List <ItemData> itemsCache, Hue h, int minZ = -1, int maxZ = -1)
        {
            // check if a minZ has been specified
            if (minZ == -1 || minZ < MinZ)
            {
                minZ = MinZ;
            }

            // check if a maxZ has been specified
            if (maxZ == -1 || maxZ > MaxZ)
            {
                maxZ = MaxZ;
            }

            // default tile size width
            int tileSize = defaultTileSize.Width;

            // tile size to use to calculate the width
            int widthTileSize = Settings.Default.useOldKRItems ? defaultTileSize.Height : tileSize;

            // calculate the image width
            int width = Math.Max(MaxX * widthTileSize + MaxY * widthTileSize - MinX * widthTileSize - MinY * widthTileSize + 200, 600);

            // calculate the image height
            int height = width * 2;

            // calculate the middle of the canvas
            int halfWidth  = width / 2;
            int halfHeight = height / 2;

            // original maxZ
            int orgMaxZ = MaxZ;

            // too big?
            if (width > 10000 || height > 10000)
            {
                return(null);
            }

            // initialize the final image
            Bitmap final = new Bitmap(width, height);

            // initialize the drawing tool
            using (Graphics g = Graphics.FromImage(final))
            {
                // set the background transparent
                g.Clear(Color.Transparent);

                // get the parts list
                List <MultiItemPart> PartsList = new List <MultiItemPart>(Parts);

                // scan all the parts in the list
                foreach (MultiItemPart p in PartsList)
                {
                    // get the item data
                    ItemData itm = itemsCache.Where(it => it.ID == p.ItemID).FirstOrDefault();

                    // item missing?
                    if (itm == null)
                    {
                        continue;
                    }

                    // the floors with the old KR art tiles are not isometric, so we need to recalculate the positon
                    if (Settings.Default.useOldKRItems && Type == MultiType.House && itm.Flags.HasFlag(ItemData.TileFlag.Surface) && !itm.Flags.HasFlag(ItemData.TileFlag.Bridge) && !itm.Flags.HasFlag(ItemData.TileFlag.StairRight))
                    {
                        p.X -= 1;
                        p.Y -= 1;
                    }

                    // ship mast are drawn incorrectly with the old KR art
                    if (Settings.Default.useOldKRItems && Type == MultiType.Boat && itm.Flags.HasFlag(ItemData.TileFlag.Foliage))
                    {
                        // set the position (based on the mast ID)
                        switch (itm.ID)
                        {
                        case 16093:
                        case 15962:
                        {
                            p.X += 1;
                            p.Y += 1;

                            break;
                        }

                        case 15980:
                        case 16098:
                        {
                            p.X += 2;
                            p.Y += 2;

                            break;
                        }
                        }
                    }

                    // signs need to be drawn higher
                    if (Type == MultiType.House && itm.Flags.HasFlag(ItemData.TileFlag.Transparent) && !itm.Flags.HasFlag(ItemData.TileFlag.Foliage))
                    {
                        p.Z += 5;
                    }

                    // fix the SA ship mast location
                    if (Type == MultiType.Boat && IsSAMast(itm.Flags, itm.Name))
                    {
                        // move the mast higher
                        p.Z = orgMaxZ + (orgMaxZ - p.Z);

                        // make sure the max Z is still correct
                        MaxZ = Math.Max(MaxZ, p.Z);
                    }
                }

                // we draw the image from bottom to top
                for (int z = MinZ; z <= MaxZ; z++)
                {
                    // and from back to front
                    for (int y = MinY; y <= MaxY; y++)
                    {
                        for (int x = MinX; x <= MaxX; x++)
                        {
                            // get the part at the current location
                            List <MultiItemPart> currParts = Parts.Where(pt => pt.X == x && pt.Y == y && pt.Z == z).Where(pt => pt.OriginalZ >= minZ && pt.OriginalZ <= maxZ).ToList();

                            // nothing in this position?
                            if (currParts == null || currParts.Count == 0)
                            {
                                continue;
                            }

                            // put the floors first then the rest
                            currParts = currParts.OrderBy(pt => itemsCache.Where(it => it.ID == pt.ItemID).FirstOrDefault() != null && (!(itemsCache.Where(it => it.ID == pt.ItemID).FirstOrDefault()).Flags.HasFlag(ItemData.TileFlag.Surface) || (itemsCache.Where(it => it.ID == pt.ItemID).FirstOrDefault()).Flags.HasFlag(ItemData.TileFlag.Bridge))).ToList();

                            // draw all the items in this tile
                            foreach (MultiItemPart p in currParts)
                            {
                                // get the item data
                                ItemData itm = itemsCache.Where(it => it.ID == p.ItemID).FirstOrDefault();

                                // missing item?
                                if (itm == null)
                                {
                                    continue;
                                }

                                // calculate where to draw the item
                                Point drawPoint = GetDrawingPosition(p, itm, width, height);

                                // get the cached tile image
                                Bitmap img = itemsImageCache.ContainsKey((int)itm.ID) ? itemsImageCache[(int)itm.ID] : null;

                                // no image in the cache?
                                if (img == null && !itemsImageCache.ContainsKey((int)itm.ID))
                                {
                                    // load the image in the cache
                                    itemsImageCache.Add((int)itm.ID, itm.GetItemImage(gamePath, Settings.Default.useOldKRItems));

                                    // get the image
                                    img = itemsImageCache[(int)itm.ID];
                                }

                                // draw the item image
                                if (img != null)
                                {
                                    // is there a hue selected?
                                    if (h.ID != 0)
                                    {
                                        // for boats we apply the color only to the dyeable parts
                                        if ((Type == MultiType.Boat && itm.Flags.HasFlag(ItemData.TileFlag.PartialHue) && !IsSAMast(itm.Flags, itm.Name)) || Type != MultiType.Boat)
                                        {
                                            // create a temporary image to apply the hue
                                            using (DirectBitmap db = new DirectBitmap(img))
                                            {
                                                // apply the hue
                                                img = db.ApplyHue(h.HueDiagram, itm.Flags.HasFlag(ItemData.TileFlag.PartialHue));
                                            }
                                        }
                                    }

                                    // are we using the old KR items and this is a floor item?
                                    if (Settings.Default.useOldKRItems && itm.Flags.HasFlag(ItemData.TileFlag.Surface) && !itm.Flags.HasFlag(ItemData.TileFlag.Bridge) && !itm.Flags.HasFlag(ItemData.TileFlag.StairRight) && !itm.Flags.HasFlag(ItemData.TileFlag.Container))
                                    {
                                        // kr floors need to be rotated 45°
                                        g.DrawImage(DirectBitmap.RotateImage(img, 45), drawPoint);
                                    }

                                    else // draw the item
                                    {
                                        g.DrawImage(img, drawPoint);
                                    }
                                }

                                // update the form
                                Application.DoEvents();
                            }
                        }
                    }
                }
            }

            return(DirectBitmap.CropImage(final, 50));
        }
        /// <summary>
        /// Floodfill gray pixels in an image
        /// </summary>
        /// <param name="bmp">Image to parse</param>
        /// <param name="pt">Starting point (must be gray)</param>
        /// <param name="levels">Hue colors table</param>
        /// <param name="handled">List of all the pixels already done</param>
        private static void FloodFillGrayPixels(DirectBitmap bmp, Point pt, List <Color> levels, ref List <Point> handled)
        {
            // create a points queue
            Queue <Point> q = new Queue <Point>();

            // add the current point to the queue
            q.Enqueue(pt);

            // keep going until the queue is empty
            while (q.Count > 0)
            {
                // get the first point
                Point n = q.Dequeue();

                // get the color for the current pixel
                Color currColor = bmp.GetPixel(n.X, n.Y);

                // if the pixel is not gray or is already been handled, we can get out (we also skil black and transparent colors)
                if (!(currColor.R == currColor.G && currColor.R == currColor.B) || ColorMatch(currColor, Color.Black) || ColorMatch(currColor, Color.FromArgb(0, 0, 0, 0)) || handled.Contains(pt))
                {
                    return;
                }

                // get the next point
                Point w = n, e = new Point(n.X + 1, n.Y);

                // keep going until the colors are within the threshold or we reach the beginning of the line
                while ((w.X >= 0) && IsPixelGrayScale(bmp.GetPixel(w.X, w.Y)))
                {
                    // replace the pixel color
                    bmp.SetPixel(w.X, w.Y, levels[bmp.GetPixel(w.X, w.Y).B]);

                    // add the pixel to the list of the handled ones
                    if (!handled.Contains(w))
                    {
                        handled.Add(w);
                    }

                    // if the previous pixel is grayscale, we put that in the queue
                    if ((w.Y > 0) && IsPixelGrayScale(bmp.GetPixel(w.X, w.Y - 1)))
                    {
                        q.Enqueue(new Point(w.X, w.Y - 1));
                    }

                    // if the next pixel is grayscale, we put that in the queue
                    if ((w.Y < bmp.Height - 1) && IsPixelGrayScale(bmp.GetPixel(w.X, w.Y + 1)))
                    {
                        q.Enqueue(new Point(w.X, w.Y + 1));
                    }

                    // move backwards in the line
                    w.X--;
                }

                // keep going until the colors are within the threshold or we reach the end of the line
                while ((e.X <= bmp.Width - 1) && IsPixelGrayScale(bmp.GetPixel(e.X, e.Y)))
                {
                    // replace the pixel color
                    bmp.SetPixel(e.X, e.Y, levels[bmp.GetPixel(e.X, e.Y).B]);

                    // add the pixel to the list of the handled ones
                    if (!handled.Contains(e))
                    {
                        handled.Add(e);
                    }

                    // check the pixel on the previous line, if it matches, we add it to the queue
                    if ((e.Y > 0) && IsPixelGrayScale(bmp.GetPixel(e.X, e.Y - 1)))
                    {
                        q.Enqueue(new Point(e.X, e.Y - 1));
                    }

                    // check the pixel in the next line, if it matches, we add it to the queue
                    if ((e.Y < bmp.Height - 1) && IsPixelGrayScale(bmp.GetPixel(e.X, e.Y + 1)))
                    {
                        q.Enqueue(new Point(e.X, e.Y + 1));
                    }

                    // move forward along the line
                    e.X++;
                }
            }
        }
        /// <summary>
        /// Save the animation in the VD file
        /// </summary>
        /// <param name="writer">binary writer attached to the file</param>
        /// <param name="headerPos">position in the file for the current animation headers</param>
        /// <param name="dir">direction to export</param>
        /// <param name="animPos">position in the file in which to write the animation data (color, frames, etc...)</param>
        /// <returns></returns>
        public bool ExportAnimationToVD(BinaryWriter writer, int dir, ref long headerPos, ref long animPos)
        {
            //// make sure the animation images are available
            //GenerateImages();

            // move to the animation header position
            writer.BaseStream.Seek(headerPos, SeekOrigin.Begin);

            // write the location of the animation data
            writer.Write((int)animPos);

            // update the header position (for the next animation/direction)
            headerPos = writer.BaseStream.Position;

            // move to the animation data address
            writer.BaseStream.Seek(animPos, SeekOrigin.Begin);

            // initialize the list of colors (EC animations only)
            List <ColourEntry> cols = new List <ColourEntry>();

            // generate the colors for this direction (EC animations only)
            if (Frames[dir * FramesPerDirection].VDFrameColors.Count <= 0)
            {
                // create the image
                m_Frames[dir * FramesPerDirection].LoadFrameImage(_ImageData, _ImageDataOffset, m_Colours);

                // load the palette
                cols = Frames[dir * FramesPerDirection].GeneratePaletteFromImage();
            }

            // scan the colors table of the current frameset (there are always 256 colors)
            for (int i = 0; i < 0x100; i++)
            {
                // write the color (from VD file)
                if (Frames[dir * FramesPerDirection].VDFrameColors.Count > 0)
                {
                    writer.Write((ushort)(Frames[dir * FramesPerDirection].VDFrameColors[i].ColorRGB555 ^ 0x8000));
                }

                else // from EC animation
                {
                    writer.Write((ushort)(cols[i].ColorRGB555 ^ 0x8000));
                }
            }

            // store the current position
            long startPos = (int)writer.BaseStream.Position;

            // write the amount of frames in this direction
            writer.Write(FramesPerDirection);

            // store this position
            long seek = (int)writer.BaseStream.Position;

            // calculate the length of the frame data
            long curr = writer.BaseStream.Position + (4 * FramesPerDirection);

            // scan the frames of this direction of the animation
            for (int f = dir * FramesPerDirection; f < (dir * FramesPerDirection) + FramesPerDirection; f++)
            {
                // move the cursor to the first address available for this frame
                writer.BaseStream.Seek(seek, SeekOrigin.Begin);

                // write the frame data length
                writer.Write((int)(curr - startPos));

                // update the position for the next frame
                seek = writer.BaseStream.Position;

                // move to write the frame data
                writer.BaseStream.Seek(curr, SeekOrigin.Begin);

                // create the frame with the animation (or forced) size
                using (DirectBitmap realFrame = CreateRealFrameImage(f, null, 0, 0, true))
                {
                    // calculate the tile center
                    short centerX = (short)(m_width - EndX);
                    short centerY = (short)(-EndY);

                    // calculate the top-left corner coordinates
                    int topX = Math.Abs((int)m_InitCoordsX - (int)m_Frames[f].InitCoordsX);
                    int topY = Math.Abs((int)m_InitCoordsY - (int)m_Frames[f].InitCoordsY);

                    // write the frame data
                    Frames[f].ExportVDImageData(writer, realFrame, centerX, centerY, topX, topY, cols);
                }

                // update the position to write the next frame data
                curr = writer.BaseStream.Position;
            }

            // calculate the length of this frameset data
            long length = writer.BaseStream.Position - animPos;

            // update the position for the next frameset
            animPos = writer.BaseStream.Position;

            // move back to the headers location
            writer.BaseStream.Seek(headerPos, SeekOrigin.Begin);

            // write the frameset size
            writer.Write((int)length);

            // write the "extra" flag (is always 0)
            writer.Write((int)0);

            // update the headers position for the next frameset
            headerPos = writer.BaseStream.Position;

            return(true);
        }