/// <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); }