public BluRaySupPicture(BluRaySupPicture subPicture)
        {
            Width             = subPicture.Width;
            Height            = subPicture.Height;
            StartTime         = subPicture.StartTime;
            EndTime           = subPicture.EndTime;
            IsForced          = subPicture.IsForced;
            CompositionNumber = subPicture.CompositionNumber;

            ObjectId     = subPicture.ObjectId;
            ImageObjects = new List <ImageObject>();
            foreach (ImageObject io in subPicture.ImageObjects)
            {
                ImageObjects.Add(io);
            }
            WindowWidth         = subPicture.WindowWidth;
            WindowHeight        = subPicture.WindowHeight;
            WindowXOffset       = subPicture.WindowXOffset;
            WindowYOffset       = subPicture.WindowYOffset;
            FramesPerSecondType = subPicture.FramesPerSecondType;
            Palettes            = new List <List <PaletteInfo> >();
            foreach (List <PaletteInfo> palette in subPicture.Palettes)
            {
                List <PaletteInfo> p = new List <PaletteInfo>();
                foreach (PaletteInfo pi in palette)
                {
                    p.Add(new PaletteInfo(pi));
                }
                Palettes.Add(p);
            }
        }
        /// <summary>
        /// parse an PDS packet which contain palette info
        /// </summary>
        /// <param name="buffer">Buffer of raw byte data, starting right after segment</param>
        /// <param name="segment">object containing info about the current segment</param>
        /// <param name="pic">SubPicture object containing info about the current caption</param>
        /// <param name="msg">reference to message string</param>
        /// <returns>number of valid palette entries (-1 for fault)</returns>
        private static int ParsePds(byte[] buffer, SupSegment segment, BluRaySupPicture pic, string[] msg)
        {
            int paletteId = buffer[0];  // 8bit palette ID (0..7)
            // 8bit palette version number (incremented for each palette change)
            int paletteUpdate = buffer[1];

            if (pic.Palettes == null)
            {
                pic.Palettes = new List <List <PaletteInfo> >();
                for (int i = 0; i < 8; i++)
                {
                    pic.Palettes.Add(new List <PaletteInfo>());
                }
            }
            if (paletteId > 7)
            {
                msg[0] = "Illegal palette id at offset " + ToolBox.ToHex(buffer, 0, 8);
                return(-1);
            }
            List <PaletteInfo> al = pic.Palettes[paletteId];

            if (al == null)
            {
                al = new List <PaletteInfo>();
            }
            PaletteInfo p = new PaletteInfo();

            p.PaletteSize   = (segment.Size - 2) / 5;
            p.PaletteBuffer = new byte[p.PaletteSize * 5];
            Buffer.BlockCopy(buffer, 2, p.PaletteBuffer, 0, p.PaletteSize * 5); // save palette buffer in palette object
            al.Add(p);
            msg[0] = "ID: " + paletteId + ", update: " + paletteUpdate + ", " + p.PaletteSize + " entries";
            return(p.PaletteSize);
        }
        /// <summary>
        /// Parse an PCS packet which contains width/height info
        /// </summary>
        /// <param name="segment">object containing info about the current segment</param>
        /// <param name="pic">SubPicture object containing info about the current caption</param>
        /// <param name="msg">reference to message string</param>
        /// <param name="buffer">Raw data buffer, starting right after segment</param>
        private static void ParsePcs(SupSegment segment, BluRaySupPicture pic, string[] msg, byte[] buffer)
        {
            if (segment.Size >= 4)
            {
                pic.Width  = BigEndianInt16(buffer, 0); // video_width
                pic.Height = BigEndianInt16(buffer, 2); // video_height
                int type = buffer[4];                   // hi nibble: frame_rate, lo nibble: reserved
                int num  = BigEndianInt16(buffer, 5);   // composition_number
                // skipped:
                // 8bit  composition_state: 0x00: normal,       0x40: acquisition point
                //                          0x80: epoch start,  0xC0: epoch continue, 6bit reserved
                // 8bit  palette_update_flag (0x80), 7bit reserved
                int palId = buffer[9];  // 8bit  palette_id_ref
                int coNum = buffer[10]; // 8bit  number_of_composition_objects (0..2)
                if (coNum > 0)
                {
                    // composition_object:
                    int objId = BigEndianInt16(buffer, 11); // 16bit object_id_ref
                    msg[0] = "palID: " + palId + ", objID: " + objId;
                    if (pic.ImageObjects == null)
                    {
                        pic.ImageObjects = new List <ImageObject>();
                    }
                    ImageObject imgObj;
                    if (objId >= pic.ImageObjects.Count)
                    {
                        imgObj = new ImageObject();
                        pic.ImageObjects.Add(imgObj);
                    }
                    else
                    {
                        imgObj = pic.GetImageObject(objId);
                    }
                    imgObj.PaletteId = palId;
                    pic.ObjectId     = objId;

                    // skipped:  8bit  window_id_ref
                    if (segment.Size >= 0x13)
                    {
                        pic.FramesPerSecondType = type;
                        // object_cropped_flag: 0x80, forced_on_flag = 0x040, 6bit reserved
                        int forcedCropped = buffer[14];
                        pic.CompositionNumber = num;
                        pic.IsForced          = ((forcedCropped & 0x40) == 0x40);
                        imgObj.XOffset        = BigEndianInt16(buffer, 15); // composition_object_horizontal_position
                        imgObj.YOffset        = BigEndianInt16(buffer, 17); // composition_object_vertical_position
                        // if (object_cropped_flag==1)
                        //      16bit object_cropping_horizontal_position
                        //      16bit object_cropping_vertical_position
                        //      16bit object_cropping_width
                        //      object_cropping_height
                    }
                }
            }
        }
        /// <summary>
        /// Checks if two SubPicture object can be merged because the time gap between them is rather small
        /// and the embedded objects seem to be identical
        /// </summary>
        /// <param name="a">first SubPicture object (earlier)</param>
        /// <param name="b">2nd SubPicture object (later)</param>
        /// <returns>return true if the SubPictures can be merged</returns>
        private static bool IsPictureMergable(BluRaySupPicture a, BluRaySupPicture b)
        {
            bool eq = false;

            if (a != null && b != null)
            {
                if (a.EndTime == 0 || b.StartTime - a.EndTime < Core.GetMergePtSdiff())
                {
                    ImageObject ao = a.ObjectIdImage;
                    ImageObject bo = b.ObjectIdImage;
                    if (ao != null && bo != null)
                    {
                        if (ao.BufferSize == bo.BufferSize && ao.Width == bo.Width && ao.Height == bo.Height)
                        {
                            eq = true;
                        }
                    }
                }
            }
            return(eq);
        }
        /// <summary>
        /// Create the binary stream representation of one caption
        /// </summary>
        /// <param name="pic">SubPicture object containing caption info</param>
        /// <param name="bm">bitmap</param>
        /// <param name="pal">palette</param>
        /// <returns>byte buffer containing the binary stream representation of one caption</returns>
        public static byte[] CreateSupFrame(BluRaySupPicture pic, Bitmap bm)
        {
            int size             = 0;
            var colorPalette     = GetBitmapPalette(bm);
            BluRaySupPalette pal = new BluRaySupPalette(colorPalette.Count);
            int k = 0;

            foreach (var kvp in colorPalette)
            {
                pal.SetColor(k, kvp.Key);
                k++;
            }

            byte[] rleBuf = EncodeImage(bm, colorPalette);

            // for some obscure reason, a packet can be a maximum 0xfffc bytes
            // since 13 bytes are needed for the header("PG", PTS, DTS, ID, SIZE)
            // there are only 0xffef bytes available for the packet
            // since the first ODS packet needs an additional 11 bytes for info
            // and the following ODS packets need 4 additional bytes, the
            // first package can store only 0xffe4 RLE buffer bytes and the
            // following packets can store 0xffeb RLE buffer bytes
            int numAddPackets;

            if (rleBuf.Length <= 0xffe4)
            {
                numAddPackets = 0; // no additional packets needed;
            }
            else
            {
                numAddPackets = 1 + (rleBuf.Length - 0xffe4) / 0xffeb;
            }

            // a typical frame consists of 8 packets. It can be enlonged by additional
            // object frames
            int palSize = colorPalette.Count;

            size  = packetHeader.Length * (8 + numAddPackets);
            size += headerPCSStart.Length + headerPCSEnd.Length;
            size += 2 * headerWDS.Length + headerODSFirst.Length;
            size += numAddPackets * headerODSNext.Length;
            size += (2 + palSize * 5) /* PDS */;
            size += rleBuf.Length;

            pic.WindowXOffset = (pic.Width - bm.Width) / 2;
            pic.WindowYOffset = pic.Height - (bm.Height + (pic.Height / 12));

            int yOfs = pic.WindowYOffset - Core.CropOfsY;

            if (yOfs < 0)
            {
                yOfs = 0;
            }
            else
            {
                int yMax = pic.Height - pic.WindowHeight - 2 * Core.CropOfsY;
                if (yOfs > yMax)
                {
                    yOfs = yMax;
                }
            }

            int h = pic.Height - 2 * Core.CropOfsY;

            byte[] buf   = new byte[size];
            int    index = 0;

            int fpsId = getFpsId(Core.fpsTrg);

            /* time (in 90kHz resolution) needed to initialize (clear) the screen buffer
             * based on the composition pixel rate of 256e6 bit/s - always rounded up */
            int frameInitTime = (pic.Width * pic.Height * 9 + 3199) / 3200;     // better use default height here

            /* time (in 90kHz resolution) needed to initialize (clear) the window area
             * based on the composition pixel rate of 256e6 bit/s - always rounded up
             * Note: no cropping etc. -> window size == image size */
            int windowInitTime = (bm.Width * bm.Height * 9 + 3199) / 3200;

            /* time (in 90kHz resolution) needed to decode the image
             * based on the decoding pixel rate of 128e6 bit/s - always rounded up  */
            int imageDecodeTime = (bm.Width * bm.Height * 9 + 1599) / 1600;

            // write PCS start
            packetHeader[10] = 0x16;                                                // ID
            int dts = (int)pic.StartTime - (frameInitTime + windowInitTime);

            ToolBox.SetDWord(packetHeader, 2, (int)pic.StartTime);                  // PTS
            ToolBox.SetDWord(packetHeader, 6, dts);                                 // DTS
            ToolBox.SetWord(packetHeader, 11, headerPCSStart.Length);               // size
            for (int i = 0; i < packetHeader.Length; i++)
            {
                buf[index++] = packetHeader[i];
            }
            ToolBox.SetWord(headerPCSStart, 0, pic.Width);
            ToolBox.SetWord(headerPCSStart, 2, h);                                  // cropped height
            ToolBox.SetByte(headerPCSStart, 4, fpsId);
            ToolBox.SetWord(headerPCSStart, 5, pic.CompositionNumber);
            headerPCSStart[14] = (byte)(pic.IsForced ? 0x40 : 0);
            ToolBox.SetWord(headerPCSStart, 15, pic.WindowXOffset);
            ToolBox.SetWord(headerPCSStart, 17, yOfs);
            for (int i = 0; i < headerPCSStart.Length; i++)
            {
                buf[index++] = headerPCSStart[i];
            }

            // write WDS
            packetHeader[10] = 0x17;                                                // ID
            int timeStamp = (int)pic.StartTime - windowInitTime;

            ToolBox.SetDWord(packetHeader, 2, timeStamp);                           // PTS (keep DTS)
            ToolBox.SetWord(packetHeader, 11, headerWDS.Length);                    // size
            for (int i = 0; i < packetHeader.Length; i++)
            {
                buf[index++] = packetHeader[i];
            }
            ToolBox.SetWord(headerWDS, 2, pic.WindowXOffset);
            ToolBox.SetWord(headerWDS, 4, yOfs);
            ToolBox.SetWord(headerWDS, 6, bm.Width);
            ToolBox.SetWord(headerWDS, 8, bm.Height);
            for (int i = 0; i < headerWDS.Length; i++)
            {
                buf[index++] = headerWDS[i];
            }

            // write PDS
            packetHeader[10] = 0x14;                                                // ID
            ToolBox.SetDWord(packetHeader, 2, dts);                                 // PTS (=DTS of PCS/WDS)
            ToolBox.SetDWord(packetHeader, 6, 0);                                   // DTS (0)
            ToolBox.SetWord(packetHeader, 11, (2 + palSize * 5));                   // size
            for (int i = 0; i < packetHeader.Length; i++)
            {
                buf[index++] = packetHeader[i];
            }
            buf[index++] = 0;
            buf[index++] = 0;
            for (int i = 0; i < palSize; i++)
            {
                buf[index++] = (byte)i;                                             // index
                buf[index++] = pal.GetY()[i];                                       // Y
                buf[index++] = pal.GetCr()[i];                                      // Cr
                buf[index++] = pal.GetCb()[i];                                      // Cb
                buf[index++] = pal.GetAlpha()[i];                                   // Alpha
            }

            // write first OBJ
            int bufSize  = rleBuf.Length;
            int rleIndex = 0;

            if (bufSize > 0xffe4)
            {
                bufSize = 0xffe4;
            }
            packetHeader[10] = 0x15;                                                // ID
            timeStamp        = dts + imageDecodeTime;
            ToolBox.SetDWord(packetHeader, 2, timeStamp);                           // PTS
            ToolBox.SetDWord(packetHeader, 6, dts);                                 // DTS
            ToolBox.SetWord(packetHeader, 11, headerODSFirst.Length + bufSize);     // size
            for (int i = 0; i < packetHeader.Length; i++)
            {
                buf[index++] = packetHeader[i];
            }
            int marker = (int)((numAddPackets == 0) ? 0xC0000000 : 0x80000000);

            ToolBox.SetDWord(headerODSFirst, 3, marker | (rleBuf.Length + 4));
            ToolBox.SetWord(headerODSFirst, 7, bm.Width);
            ToolBox.SetWord(headerODSFirst, 9, bm.Height);
            for (int i = 0; i < headerODSFirst.Length; i++)
            {
                buf[index++] = headerODSFirst[i];
            }
            for (int i = 0; i < bufSize; i++)
            {
                buf[index++] = rleBuf[rleIndex++];
            }

            // write additional OBJ packets
            bufSize = rleBuf.Length - bufSize;     // remaining bytes to write
            for (int p = 0; p < numAddPackets; p++)
            {
                int psize = bufSize;
                if (psize > 0xffeb)
                {
                    psize = 0xffeb;
                }
                packetHeader[10] = 0x15;                                                // ID (keep DTS & PTS)
                ToolBox.SetWord(packetHeader, 11, headerODSNext.Length + psize);        // size
                for (int i = 0; i < packetHeader.Length; i++)
                {
                    buf[index++] = packetHeader[i];
                }
                for (int i = 0; i < headerODSNext.Length; i++)
                {
                    buf[index++] = headerODSNext[i];
                }
                for (int i = 0; i < psize; i++)
                {
                    buf[index++] = rleBuf[rleIndex++];
                }
                bufSize -= psize;
            }

            // write END
            packetHeader[10] = (byte)0x80;                                          // ID
            ToolBox.SetDWord(packetHeader, 6, 0);                                   // DTS (0) (keep PTS of ODS)
            ToolBox.SetWord(packetHeader, 11, 0);                                   // size
            for (int i = 0; i < packetHeader.Length; i++)
            {
                buf[index++] = packetHeader[i];
            }

            // write PCS end
            packetHeader[10] = 0x16;                                                // ID
            ToolBox.SetDWord(packetHeader, 2, (int)pic.EndTime);                    // PTS
            dts = (int)pic.StartTime - 1;
            ToolBox.SetDWord(packetHeader, 6, dts);                                 // DTS
            ToolBox.SetWord(packetHeader, 11, headerPCSEnd.Length);                 // size
            for (int i = 0; i < packetHeader.Length; i++)
            {
                buf[index++] = packetHeader[i];
            }
            ToolBox.SetWord(headerPCSEnd, 0, pic.Width);
            ToolBox.SetWord(headerPCSEnd, 2, h);                                    // cropped height
            ToolBox.SetByte(headerPCSEnd, 4, fpsId);
            ToolBox.SetWord(headerPCSEnd, 5, pic.CompositionNumber + 1);
            for (int i = 0; i < headerPCSEnd.Length; i++)
            {
                buf[index++] = headerPCSEnd[i];
            }

            // write WDS
            packetHeader[10] = 0x17;                                                // ID
            timeStamp        = (int)pic.EndTime - windowInitTime;
            ToolBox.SetDWord(packetHeader, 2, timeStamp);                           // PTS (keep DTS of PCS)
            ToolBox.SetWord(packetHeader, 11, headerWDS.Length);                    // size
            for (int i = 0; i < packetHeader.Length; i++)
            {
                buf[index++] = packetHeader[i];
            }
            ToolBox.SetWord(headerWDS, 2, pic.WindowXOffset);
            ToolBox.SetWord(headerWDS, 4, yOfs);
            ToolBox.SetWord(headerWDS, 6, bm.Width);
            ToolBox.SetWord(headerWDS, 8, bm.Height);
            for (int i = 0; i < headerWDS.Length; i++)
            {
                buf[index++] = headerWDS[i];
            }

            // write END
            packetHeader[10] = (byte)0x80;                                          // ID
            ToolBox.SetDWord(packetHeader, 2, dts);                                 // PTS (DTS of end PCS)
            ToolBox.SetDWord(packetHeader, 6, 0);                                   // DTS (0)
            ToolBox.SetWord(packetHeader, 11, 0);                                   // size
            for (int i = 0; i < packetHeader.Length; i++)
            {
                buf[index++] = packetHeader[i];
            }

            return(buf);
        }
        /// <summary>
        /// decode palette from the input stream
        /// </summary>
        /// <param name="pic">SubPicture object containing info about the current caption</param>
        /// <returns>Palette object</returns>
        public BluRaySupPalette DecodePalette(BluRaySupPalette defaultPalette)
        {
            BluRaySupPicture pic     = this;
            bool             fadeOut = false;

            if (pic.Palettes.Count == 0 || pic.ObjectIdImage.PaletteId >= pic.Palettes.Count)
            {
                System.Diagnostics.Debug.Print("Palette not found in objectID=" + pic.ObjectId + " PaletteId=" + pic.ObjectIdImage.PaletteId + "!");
                if (defaultPalette == null)
                {
                    return(new BluRaySupPalette(256, Core.UsesBt601()));
                }
                else
                {
                    return(new BluRaySupPalette(defaultPalette));
                }
            }
            List <PaletteInfo> pl      = pic.Palettes[pic.ObjectIdImage.PaletteId];
            BluRaySupPalette   palette = new BluRaySupPalette(256, Core.UsesBt601());

            // by definition, index 0xff is always completely transparent
            // also all entries must be fully transparent after initialization

            for (int j = 0; j < pl.Count; j++)
            {
                PaletteInfo p     = pl[j];
                int         index = 0;

                for (int i = 0; i < p.PaletteSize; i++)
                {
                    // each palette entry consists of 5 bytes
                    int palIndex = p.PaletteBuffer[index];
                    int y = p.PaletteBuffer[++index];
                    int cr, cb;
                    if (Core.GetSwapCrCb())
                    {
                        cb = p.PaletteBuffer[++index];
                        cr = p.PaletteBuffer[++index];
                    }
                    else
                    {
                        cr = p.PaletteBuffer[++index];
                        cb = p.PaletteBuffer[++index];
                    }
                    int alpha = p.PaletteBuffer[++index];

                    int alphaOld = palette.GetAlpha(palIndex);
                    // avoid fading out
                    if (alpha >= alphaOld || alpha == 0)
                    {
                        if (alpha < Core.GetAlphaCrop())
                        {// to not mess with scaling algorithms, make transparent color black
                            y  = 16;
                            cr = 128;
                            cb = 128;
                        }
                        palette.SetAlpha(palIndex, alpha);
                    }
                    else
                    {
                        fadeOut = true;
                    }

                    palette.SetYCbCr(palIndex, y, cb, cr);
                    index++;
                }
            }
            if (fadeOut)
            {
                System.Diagnostics.Debug.Print("fade out detected -> patched palette\n");
            }
            return(palette);
        }
        /// <summary>
        /// Can be used with e.g. MemoryStream or FileStream
        /// </summary>
        /// <param name="ms">memory stream containing sup data</param>
        /// <param name="log">Text parser log</param>
        public static List <BluRaySupPicture> ParseBluRaySup(Stream ms, StringBuilder log, bool fromMatroskaFile)
        {
            SupSegment              segment;
            BluRaySupPicture        pic         = null;
            BluRaySupPicture        picLast     = null;
            BluRaySupPicture        picTmp      = null;
            List <BluRaySupPicture> subPictures = new List <BluRaySupPicture>();
            int              odsCtr             = 0;
            int              pdsCtr             = 0;
            int              odsCtrOld          = 0;
            int              pdsCtrOld          = 0;
            int              compNum            = -1;
            int              compNumOld         = -1;
            int              compCount          = 0;
            long             ptsPcs             = 0;
            bool             paletteUpdate      = false;
            CompositionState cs = CompositionState.Invalid;

            ms.Position = 0;
            long      position   = 0;
            int       i          = 0;
            const int headerSize = 13;

            byte[] buffer;
            while (position < ms.Length)
            {
                string[] so = new string[1]; // hack to return string

                ms.Seek(position, SeekOrigin.Begin);

                if (fromMatroskaFile)
                {
                    buffer = new byte[3];
                    ms.Read(buffer, 0, buffer.Length);
                    segment   = ReadSegmentFromMatroska(buffer, log);
                    position += 3;
                }
                else
                {
                    buffer = new byte[headerSize];
                    ms.Read(buffer, 0, buffer.Length);
                    segment   = ReadSegment(buffer, log);
                    position += headerSize;
                }

                buffer = new byte[segment.Size];
                ms.Read(buffer, 0, buffer.Length);
                log.Append(i + ": ");
                switch (segment.Type)
                {
                case 0x14:     // Palette
                    log.Append(string.Format("0x14 - Palette - PDS offset={0} size={1}", position, segment.Size));

                    if (compNum != compNumOld)
                    {
                        if (pic != null)
                        {
                            so[0] = null;
                            int ps = ParsePds(buffer, segment, pic, so);
                            if (ps >= 0)
                            {
                                log.AppendLine(", " + so[0]);
                                if (ps > 0)     // don't count empty palettes
                                {
                                    pdsCtr++;
                                }
                            }
                            else
                            {
                                log.AppendLine();
                                log.AppendLine(so[0]);
                            }
                        }
                        else
                        {
                            log.AppendLine();
                            log.AppendLine("missing PTS start -> ignored");
                        }
                    }
                    else
                    {
                        log.AppendLine(", comp # unchanged -> ignored");
                    }
                    break;

                case 0x15:     // Image bitmap data
                    log.Append(string.Format("0x15 - bitmap data - ODS offset={0} size={1}", position, segment.Size));

                    if (compNum != compNumOld)
                    {
                        if (!paletteUpdate)
                        {
                            if (pic != null)
                            {
                                so[0] = null;
                                if (ParseOds(buffer, segment, pic, so))
                                {
                                    odsCtr++;
                                }
                                if (so[0] != null)
                                {
                                    log.Append(", " + so[0]);
                                }
                                log.AppendLine(", img size: " + pic.Width + "*" + pic.Height);
                            }
                            else
                            {
                                log.AppendLine();
                                log.AppendLine("missing PTS start -> ignored");
                            }
                        }
                        else
                        {
                            log.AppendLine();
                            log.AppendLine("palette update only -> ignored");
                        }
                    }
                    else
                    {
                        log.AppendLine(", comp # unchanged -> ignored");
                    }
                    break;

                case 0x16:
                    log.Append(string.Format("0x16 - Time codes, offset={0} size={1}", position, segment.Size));

                    compNum       = BigEndianInt16(buffer, 5);
                    cs            = GetCompositionState(buffer[7]);
                    paletteUpdate = buffer[8] == 0x80;
                    ptsPcs        = segment.PtsTimestamp;
                    if (segment.Size >= 0x13)
                    {
                        compCount = 1;     // could be also 2, but we'll ignore this for the moment
                    }
                    else
                    {
                        compCount = 0;
                    }
                    if (cs == CompositionState.Invalid)
                    {
                        log.AppendLine("Illegal composition state at offset " + position);
                    }
                    else if (cs == CompositionState.EpochStart)
                    {
                        //new frame
                        if (subPictures.Count > 0 && (odsCtr == 0 || pdsCtr == 0))
                        {
                            log.AppendLine("missing PDS/ODS: last epoch is discarded");
                            subPictures.RemoveAt(subPictures.Count - 1);
                            compNumOld = compNum - 1;
                            if (subPictures.Count > 0)
                            {
                                picLast = subPictures[subPictures.Count - 1];
                            }
                            else
                            {
                                picLast = null;
                            }
                        }
                        else
                        {
                            picLast = pic;
                        }

                        pic = new BluRaySupPicture();
                        subPictures.Add(pic);     // add to list
                        pic.StartTime = segment.PtsTimestamp;
                        log.Append("#> " + (subPictures.Count) + " (" + ToolBox.PtsToTimeString(pic.StartTime) + ")");

                        so[0] = null;
                        ParsePcs(segment, pic, so, buffer);
                        // fix end time stamp of previous pic if still missing
                        if (picLast != null && picLast.EndTime == 0)
                        {
                            picLast.EndTime = pic.StartTime;
                        }

                        if (so[0] != null)
                        {
                            log.Append(", " + so[0]);
                        }
                        log.Append(", PTS start: " + ToolBox.PtsToTimeString(pic.StartTime));
                        log.AppendLine(", screen size: " + pic.Width + "*" + pic.Height);
                        odsCtr    = 0;
                        pdsCtr    = 0;
                        odsCtrOld = 0;
                        pdsCtrOld = 0;
                        picTmp    = null;
                    }
                    else
                    {
                        if (pic == null)
                        {
                            log.AppendLine(" Missing start of epoch at offset " + position);
                            break;
                        }
                        log.Append("PCS ofs:" + ToolBox.ToHex(buffer, 0, 8) + ", ");
                        switch (cs)
                        {
                        case CompositionState.EpochContinue:
                            log.AppendLine(" CONT, ");
                            break;

                        case CompositionState.AcquPoint:
                            log.AppendLine(" ACQU, ");
                            break;

                        case CompositionState.Normal:
                            log.AppendLine(" NORM, ");
                            break;
                        }
                        log.Append("size: " + segment.Size + ", comp#: " + compNum + ", forced: " + pic.IsForced);
                        if (compNum != compNumOld)
                        {
                            so[0] = null;
                            // store state to be able to revert to it
                            picTmp         = new BluRaySupPicture(pic); // deep copy
                            picTmp.EndTime = ptsPcs;
                            // create new pic
                            ParsePcs(segment, pic, so, buffer);
                        }
                        if (so[0] != null)
                        {
                            log.Append(", " + so[0]);
                        }
                        log.AppendLine(", pal update: " + paletteUpdate);
                        log.AppendLine("PTS: " + ToolBox.PtsToTimeString(segment.PtsTimestamp));
                    }
                    break;

                case 0x17:
                    log.Append(string.Format("0x17 - WDS offset={0} size={1}", position, segment.Size));

                    int x      = BigEndianInt16(buffer, 2);
                    int y      = BigEndianInt16(buffer, 4);
                    int width  = BigEndianInt16(buffer, 6);
                    int height = BigEndianInt16(buffer, 8);

                    log.AppendLine(string.Format(", width:{0}, height:{1}   x,y={2},{3}", width, height, x, y));
                    break;

                case 0x80:
                    log.Append(string.Format("0x80 - END offset={0} size={1}", position, segment.Size));

                    // decide whether to store this last composition section as caption or merge it
                    if (cs == CompositionState.EpochStart)
                    {
                        if (compCount > 0 && odsCtr > odsCtrOld && compNum != compNumOld && IsPictureMergable(picLast, pic))
                        {
                            // the last start epoch did not contain any (new) content
                            // and should be merged to the previous frame
                            subPictures.RemoveAt(subPictures.Count - 1);
                            pic = picLast;
                            if (subPictures.Count > 0)
                            {
                                picLast = subPictures[subPictures.Count - 1];
                            }
                            else
                            {
                                picLast = null;
                            }
                            log.AppendLine("#< caption merged");
                        }
                    }
                    else
                    {
                        long startTime = 0;
                        if (pic != null)
                        {
                            startTime     = pic.StartTime; // store
                            pic.StartTime = ptsPcs;        // set for testing merge
                        }

                        if (compCount > 0 && odsCtr > odsCtrOld && compNum != compNumOld && !IsPictureMergable(picTmp, pic))
                        {
                            // last PCS should be stored as separate caption
                            if (odsCtr - odsCtrOld > 1 || pdsCtr - pdsCtrOld > 1)
                            {
                                log.AppendLine("multiple PDS/ODS definitions: result may be erratic");
                            }
                            // replace pic with picTmp (deepCopy created before new PCS)
                            subPictures[subPictures.Count - 1] = picTmp; // replace in list
                            picLast = picTmp;
                            subPictures.Add(pic);                        // add to list
                            log.AppendLine("#< " + (subPictures.Count) + " (" + ToolBox.PtsToTimeString(pic.StartTime) + ")");
                            odsCtrOld = odsCtr;
                        }
                        else
                        {
                            if (pic != null)
                            {
                                // merge with previous pic
                                pic.StartTime = startTime;     // restore
                                pic.EndTime   = ptsPcs;
                                // for the unlikely case that forced flag changed during one captions
                                if (picTmp != null && picTmp.IsForced)
                                {
                                    pic.IsForced = true;
                                }

                                if (pdsCtr > pdsCtrOld || paletteUpdate)
                                {
                                    log.AppendLine("palette animation: result may be erratic\n");
                                }
                            }
                            else
                            {
                                log.AppendLine("end without at least one epoch start");
                            }
                        }
                    }

                    pdsCtrOld  = pdsCtr;
                    compNumOld = compNum;
                    break;

                default:
                    log.AppendLine(string.Format("0x?? - END offset={0} UNKOWN SEGMENT TYPE={1}", position, segment.Type));
                    break;
                }
                log.AppendLine();
                position += segment.Size;
                i++;
            }
            //    File.WriteAllText(@"C:\Users\Nikse\Desktop\Blu-Ray Sup\log.txt", log.ToString());
            return(subPictures);
        }
        /// <summary>
        /// parse an ODS packet which contain the image data
        /// </summary>
        /// <param name="buffer">raw byte date, starting right after segment</param>
        /// <param name="segment">object containing info about the current segment</param>
        /// <param name="pic">SubPicture object containing info about the current caption</param>
        /// <param name="msg">reference to message string</param>
        /// <returns>true if this is a valid new object (neither invalid nor a fragment)</returns>
        private static bool ParseOds(byte[] buffer, SupSegment segment, BluRaySupPicture pic, string[] msg)
        {
            ImageObjectFragment info;

            int objId  = BigEndianInt16(buffer, 0); // 16bit object_id
            int objVer = buffer[2];                 // 16bit object_id nikse - index 2 or 1???
            int objSeq = buffer[3];                 // 8bit  first_in_sequence (0x80),
            // last_in_sequence (0x40), 6bits reserved
            bool first = (objSeq & 0x80) == 0x80;
            bool last  = (objSeq & 0x40) == 0x40;

            if (pic.ImageObjects == null)
            {
                pic.ImageObjects = new List <ImageObject>();
            }
            ImageObject imgObj;

            if (objId >= pic.ImageObjects.Count)
            {
                imgObj = new ImageObject();
                pic.ImageObjects.Add(imgObj);
            }
            else
            {
                imgObj = pic.GetImageObject(objId);
            }

            if (imgObj.Fragments == null || first)
            {   // 8bit  object_version_number
                // skipped:
                //  24bit object_data_length - full RLE buffer length (including 4 bytes size info)
                int width  = BigEndianInt16(buffer, 7);     // object_width
                int height = BigEndianInt16(buffer, 9);     // object_height

                if (width <= pic.Width && height <= pic.Height)
                {
                    imgObj.Fragments     = new List <ImageObjectFragment>();
                    info                 = new ImageObjectFragment();
                    info.ImagePacketSize = segment.Size - 11; // Image packet size (image bytes)
                    info.ImageBuffer     = new byte[info.ImagePacketSize];
                    Buffer.BlockCopy(buffer, 11, info.ImageBuffer, 0, info.ImagePacketSize);
                    imgObj.Fragments.Add(info);
                    imgObj.BufferSize = info.ImagePacketSize;
                    imgObj.Height     = height;
                    imgObj.Width      = width;
                    msg[0]            = "ID: " + objId + ", update: " + objVer + ", seq: " + (first ? "first" : "") +
                                        ((first && last) ? "/" : "") + (last ? "" + "last" : "");
                    return(true);
                }
                System.Diagnostics.Debug.Print("Invalid image size - ignored");
                return(false);
            }
            // object_data_fragment
            // skipped:
            //  16bit object_id
            //  8bit  object_version_number
            //  8bit  first_in_sequence (0x80), last_in_sequence (0x40), 6bits reserved
            info = new ImageObjectFragment();
            info.ImagePacketSize = segment.Size - 4;
            info.ImageBuffer     = new byte[info.ImagePacketSize];
            Buffer.BlockCopy(buffer, 4, info.ImageBuffer, 0, info.ImagePacketSize);
            imgObj.Fragments.Add(info);
            imgObj.BufferSize += info.ImagePacketSize; // total size (may contain several packets)
            msg[0]             = "ID: " + objId + ", update: " + objVer + ", seq: " + (first ? "first" : "") + ((first && last) ? "/" : "") + (last ? "" + "last" : "");
            return(false);
        }