/// <summary> /// YTSubConverter supports multiple shadow types (and colors) on one subtitle by duplicating it as necessary /// </summary> private int ExpandLineForMultiShadows(int lineIndex) { Line line = Lines[lineIndex]; int maxNumShadows = line.Sections.Max(s => s.ShadowColors.Count); if (maxNumShadows <= 1) { return(1); } List <List <ShadowType> > lineLayerShadowTypes = new List <List <ShadowType> >(); ShadowType[] orderedShadowTypes = { ShadowType.SoftShadow, ShadowType.HardShadow, ShadowType.Bevel, ShadowType.Glow }; foreach (Section section in line.Sections) { List <ShadowType> sectionLayerShadowTypes = new List <ShadowType>(); foreach (ShadowType shadowType in orderedShadowTypes) { if (section.ShadowColors.ContainsKey(shadowType)) { sectionLayerShadowTypes.Add(shadowType); } } lineLayerShadowTypes.Add(sectionLayerShadowTypes); } Lines.RemoveAt(lineIndex); for (int layerIdx = 0; layerIdx < maxNumShadows; layerIdx++) { Line shadowLine = (Line)line.Clone(); for (int sectionIdx = 0; sectionIdx < shadowLine.Sections.Count; sectionIdx++) { Section section = shadowLine.Sections[sectionIdx]; List <ShadowType> sectionLayerShadowTypes = lineLayerShadowTypes[sectionIdx]; if (layerIdx > 0) { section.BackColor = ColorUtil.ChangeColorAlpha(section.BackColor, 0); } if (layerIdx < maxNumShadows - 1) { section.ForeColor = ColorUtil.ChangeColorAlpha(section.ForeColor, 0); } if (layerIdx < sectionLayerShadowTypes.Count) { section.ShadowColors.RemoveAll(t => t != sectionLayerShadowTypes[layerIdx]); } else { section.ShadowColors.Clear(); } } Lines.Insert(lineIndex + layerIdx, shadowLine); } return(maxNumShadows); }
/// <summary> /// The mobile apps have an unchangeable black background, meaning dark text is unreadable there. /// As a workaround, we overlap the dark subtitle with an invisible bright one: this way, we /// get the custom background and dark text on PC, and a black background and bright text /// on Android (because the Android app doesn't support transparency). /// Sadly, this trick doesn't work for iOS: that one supports (only) text transparency, /// meaning our bright yet invisible subtitle doesn't show up there. /// </summary> private int ExpandLineForDarkText(int lineIdx) { Line line = Lines[lineIdx]; if (!line.Sections.Any(s => s.ForeColor.A > 0 && ColorUtil.IsDark(s.ForeColor))) { return(1); } Line brightLine = (Line)line.Clone(); foreach (Section section in brightLine.Sections) { if (section.ForeColor.A > 0 && ColorUtil.IsDark(section.ForeColor)) { section.ForeColor = ColorUtil.Brighten(section.ForeColor); } section.ForeColor = ColorUtil.ChangeColorAlpha(section.ForeColor, 0); section.BackColor = ColorUtil.ChangeColorAlpha(section.BackColor, 0); section.ShadowColors.Clear(); } Lines.Insert(lineIdx + 1, brightLine); return(2); }
public override void Handle(AssTagContext context, string arg) { int alpha = !string.IsNullOrEmpty(arg) ? 255 - (ParseHex(arg) & 255) : context.Style.PrimaryColor.A; context.Section.ForeColor = ColorUtil.ChangeColorAlpha(context.Section.ForeColor, alpha); context.Section.Animations.RemoveAll(a => a is ForeColorAnimation); }
// Aegisub doesn't display the background box for certain parts of a subtitle: // - Whitespace at the start of a line of text // - Whitespace at the end of a line of text // - Sections that consist only of whitespace // YouTube displays the background box across the entire subtitle, however, // so for an accurate visualization, we need to enclose whitespace blocks in // other characters to "protect" them. private static void ProtectWhitespace(AssLine line) { for (int i = line.Sections.Count - 1; i >= 0; i--) { AssSection section = (AssSection)line.Sections[i]; if (Regex.IsMatch(section.Text, @"^[\r\n]+$")) { continue; } Match match = Regex.Match(section.Text, @"^([ \xA0]*)(.*?)([ \xA0]*)$"); if (match.Groups[1].Length == section.Text.Length) { // If the entire section is just whitespace, we have to use a non-whitespace character to make its background visible section.Text = CreateProtectedWhitespaceString(section.Text.Length, "."); section.ForeColor = ColorUtil.ChangeColorAlpha(section.ForeColor, 0); section.ShadowColors.Clear(); } else { // Otherwise, we can use non-breaking spaces (which don't normally work, but do work in our case because we later write // them out using Aegisub's \h sequence) section.Text = CreateProtectedWhitespaceString(match.Groups[1].Length, "\xA0") + match.Groups[2].Value + CreateProtectedWhitespaceString(match.Groups[3].Length, "\xA0"); } } }
public override void Handle(AssTagContext context, string arg) { int alpha = 255 - ParseHex(arg); context.Section.ForeColor = ColorUtil.ChangeColorAlpha(context.Section.ForeColor, alpha); context.Section.Animations.RemoveAll(a => a is ForeColorAnimation); }
private static Color MultiplyColorAlpha(Color color, float factor) { if (color.IsEmpty) { return(color); } return(ColorUtil.ChangeColorAlpha(color, (int)(color.A * factor))); }
private static void HandleTransformShadowAlphaTag(AssTagContext context, ShadowType shadowType, DateTime startTime, DateTime endTime, float accel, string arg) { ShadowColorAnimation anim = FetchColorAnimation( context, startTime, endTime, a => a.ShadowType == shadowType, s => s.ShadowColors[shadowType], (s, e, c) => new ShadowColorAnimation(shadowType, s, c, e, c, accel) ); anim.EndColor = ColorUtil.ChangeColorAlpha(anim.EndColor, 255 - (ParseHex(arg) & 255)); }
public override void Handle(AssTagContext context, string arg) { int alpha = 255 - ParseHex(arg); context.Section.ForeColor = ColorUtil.ChangeColorAlpha(context.Section.ForeColor, alpha); context.Section.SecondaryColor = ColorUtil.ChangeColorAlpha(context.Section.SecondaryColor, alpha); context.Section.BackColor = ColorUtil.ChangeColorAlpha(context.Section.BackColor, alpha); foreach (ShadowType shadowType in context.Section.ShadowColors.Keys.ToList()) { context.Section.ShadowColors[shadowType] = ColorUtil.ChangeColorAlpha(context.Section.ShadowColors[shadowType], alpha); } context.Section.Animations.RemoveAll(a => a is ColorAnimation); }
private (int, Section) ReadPen(XmlElement elem) { int id = elem.GetIntAttribute("id") ?? 0; Section pen = new Section(); int fontStyleId = elem.GetIntAttribute("fs") ?? 0; pen.Font = GetFontName(fontStyleId); pen.Scale = GetRealFontScale(elem.GetIntAttribute("sz") ?? 100); pen.Offset = GetOffsetType(elem.GetIntAttribute("of") ?? 1); pen.Bold = Convert.ToBoolean(elem.GetIntAttribute("b") ?? 0); pen.Italic = Convert.ToBoolean(elem.GetIntAttribute("i") ?? 0); pen.Underline = Convert.ToBoolean(elem.GetIntAttribute("u") ?? 0); Color fc = ColorUtil.FromHtml(elem.Attributes["fc"]?.Value ?? "#FFFFFF"); int fo = elem.GetIntAttribute("fo") ?? 254; pen.ForeColor = ColorUtil.ChangeColorAlpha(fc, fo); Color bc = ColorUtil.FromHtml(elem.Attributes["bc"]?.Value ?? "#080808"); int bo = elem.GetIntAttribute("bo") ?? 192; pen.BackColor = ColorUtil.ChangeColorAlpha(bc, bo); int et = elem.GetIntAttribute("et") ?? 0; if (et != 0) { ShadowType shadowType = GetEdgeType(et); Color shadowColor; XmlAttribute ecAttr = elem.Attributes["ec"]; if (ecAttr != null) { shadowColor = ColorUtil.ChangeColorAlpha(ColorUtil.FromHtml(ecAttr.Value), 254); } else { shadowColor = Color.FromArgb(fo, 0x22, 0x22, 0x22); } pen.ShadowColors.Add(shadowType, shadowColor); } pen.RubyPart = GetRubyPart(elem.GetIntAttribute("rb") ?? 0); pen.Packed = Convert.ToBoolean(elem.GetIntAttribute("hg") ?? 0); return(id, pen); }
private static void HandleTransformOutlineAlphaTag(AssTagContext context, DateTime startTime, DateTime endTime, float accel, string arg) { if (!context.Style.HasOutline) { return; } if (context.Style.OutlineIsBox) { BackColorAnimation anim = FetchColorAnimation(context, startTime, endTime, s => s.BackColor, (s, e, c) => new BackColorAnimation(s, c, e, c, accel)); anim.EndColor = ColorUtil.ChangeColorAlpha(anim.EndColor, 255 - (ParseHex(arg) & 255)); } else { HandleTransformShadowAlphaTag(context, ShadowType.Glow, startTime, endTime, accel, arg); } }
public override void Handle(AssTagContext context, string arg) { if (!context.Style.HasShadow) { return; } int alpha = 255 - ParseHex(arg); foreach (KeyValuePair <ShadowType, Color> shadowColor in context.Section.ShadowColors.ToList()) { if (shadowColor.Key != ShadowType.Glow || !context.Style.HasOutline || context.Style.HasOutlineBox) { context.Section.ShadowColors[shadowColor.Key] = ColorUtil.ChangeColorAlpha(shadowColor.Value, alpha); } } context.Section.Animations.RemoveAll(a => a is ShadowColorAnimation); }
/// <summary> /// We should never use 255 for a foreground or background opacity. The reason is that if we do, the upload process /// will remove the attribute from the file. For such removed attributes, the player is free to use its own /// default settings... which may not match what we want. For example, if the background opacity is lost because /// we set it to 255, the PC player will use its default setting of 191. What's more, YouTube allows users to /// customize the default settings - in a small window that nobody knows about, with keyboard shortcuts that are /// easy to trigger by accident. The result is that some users accidentally configured a low, hard-to-read /// foreground opacity and don't know how to change it back. /// In addition, we need to avoid pure white (#FFFFFF) for the foreground color because, even though this will /// remain in the file after uploading (if the foreground opacity is < 255), the Android app will still /// ignore it and use the foreground color of the preceding section. /// </summary> private void LimitColors(int lineIndex) { Line line = Lines[lineIndex]; foreach (Section section in line.Sections) { if ((section.ForeColor.ToArgb() & 0xFFFFFF) == 0xFFFFFF) { section.ForeColor = Color.FromArgb(section.ForeColor.A << 24 | 0xFEFEFE); } if (section.ForeColor.A == 255) { section.ForeColor = ColorUtil.ChangeColorAlpha(section.ForeColor, 254); } if (section.BackColor.A == 255) { section.BackColor = ColorUtil.ChangeColorAlpha(section.BackColor, 254); } // YouTube doesn't have a shadow opacity attribute, so explicitly set the opacity to 255 to avoid creating // superfluous <pen>s later on (pens that only differ in shadow opacity). The exception is #222222 which // does support opacity, in a way. List <ShadowType> shadowTypesToChange = null; foreach (KeyValuePair <ShadowType, Color> shadowColor in section.ShadowColors) { if (shadowColor.Value.A != 254 && ((shadowColor.Value.ToArgb() & 0xFFFFFF) != 0x222222 || shadowColor.Value.A != section.ForeColor.A)) { (shadowTypesToChange ??= new List <ShadowType>()).Add(shadowColor.Key); } } if (shadowTypesToChange == null) { continue; } foreach (ShadowType shadowType in shadowTypesToChange) { section.ShadowColors[shadowType] = ColorUtil.ChangeColorAlpha(section.ShadowColors[shadowType], 254); } } }
public override void Handle(AssTagContext context, string arg) { if (!context.Style.HasOutline) { return; } int alpha = !string.IsNullOrEmpty(arg) ? 255 - (ParseHex(arg) & 255) : context.Style.OutlineColor.A; if (context.Style.OutlineIsBox) { context.Section.BackColor = ColorUtil.ChangeColorAlpha(context.Section.BackColor, alpha); context.Section.Animations.RemoveAll(a => a is BackColorAnimation); } else { context.Section.ShadowColors[ShadowType.Glow] = ColorUtil.ChangeColorAlpha(context.Section.ShadowColors[ShadowType.Glow], alpha); context.Section.Animations.RemoveAll(a => a is ShadowColorAnimation); } }
private static void HandleTransformSecondaryAlphaTag(AssTagContext context, DateTime startTime, DateTime endTime, float accel, string arg) { SecondaryColorAnimation anim = FetchColorAnimation(context, startTime, endTime, s => s.SecondaryColor, (s, e, c) => new SecondaryColorAnimation(s, c, e, c, accel)); anim.EndColor = ColorUtil.ChangeColorAlpha(anim.EndColor, 255 - (ParseHex(arg) & 255)); }
private static void HandleTransformForeAlphaTag(AssTagContext context, DateTime startTime, DateTime endTime, int accel, string arg) { ForeColorAnimation anim = FetchColorAnimation(context, startTime, endTime, s => s.ForeColor, (s, e, c) => new ForeColorAnimation(s, c, e, c)); anim.EndColor = ColorUtil.ChangeColorAlpha(anim.EndColor, 255 - ParseHex(arg)); }