Merge "Adjust the background text image width to reduce its size"

This commit is contained in:
Tianjie Xu 2018-11-15 05:47:54 +00:00 committed by Gerrit Code Review
commit 4ef9cb27eb
2 changed files with 82 additions and 27 deletions

View file

@ -101,7 +101,7 @@ TEST_P(ResourcesTest, ValidateLocale) {
EXPECT_LT(0, len) << "Locale string should be non-empty."; EXPECT_LT(0, len) << "Locale string should be non-empty.";
EXPECT_NE(0, row[5]) << "Locale string is missing."; EXPECT_NE(0, row[5]) << "Locale string is missing.";
ASSERT_GT(png_->height(), y + 1 + h) << "Locale: " << kLocale << " is not found in the file."; ASSERT_GE(png_->height(), y + 1 + h) << "Locale: " << kLocale << " is not found in the file.";
char* loc = reinterpret_cast<char*>(&row[5]); char* loc = reinterpret_cast<char*>(&row[5]);
if (matches_locale(loc, kLocale.c_str())) { if (matches_locale(loc, kLocale.c_str())) {
EXPECT_TRUE(android::base::StartsWith(loc, kLocale)); EXPECT_TRUE(android::base::StartsWith(loc, kLocale));

View file

@ -60,8 +60,9 @@ public class ImageGenerator {
// This is the canvas we used to draw texts. // This is the canvas we used to draw texts.
private BufferedImage mBufferedImage; private BufferedImage mBufferedImage;
// The width in pixels of our image. Once set, its value won't change. // The width in pixels of our image. The value will be adjusted once when we calculate the
private final int mImageWidth; // maximum width to fit the wrapped text strings.
private int mImageWidth;
// The current height in pixels of our image. We will adjust the value when drawing more texts. // The current height in pixels of our image. We will adjust the value when drawing more texts.
private int mImageHeight; private int mImageHeight;
@ -79,6 +80,9 @@ public class ImageGenerator {
// The directory that contains all the needed font files (e.g. ttf, otf, ttc files). // The directory that contains all the needed font files (e.g. ttf, otf, ttc files).
private final String mFontDirPath; private final String mFontDirPath;
// Align the text in the center of the image.
private final boolean mCenterAlignment;
// An explicit map from language to the font name to use. // An explicit map from language to the font name to use.
// The map is extracted from frameworks/base/data/fonts/fonts.xml. // The map is extracted from frameworks/base/data/fonts/fonts.xml.
// And the language-subtag-registry is found in: // And the language-subtag-registry is found in:
@ -130,7 +134,9 @@ public class ImageGenerator {
}; };
// Languages that breaks on arbitrary characters. // Languages that breaks on arbitrary characters.
// TODO(xunchang) switch to icu library if possible. // TODO(xunchang) switch to icu library if possible. For example, for Thai and Khmer, there is
// no space between words; and word breaking is based on grammatical analysis and on word
// matching in dictionaries.
private static final Set<String> LOGOGRAM_LANGUAGE = private static final Set<String> LOGOGRAM_LANGUAGE =
new HashSet<String>() { new HashSet<String>() {
{ {
@ -138,6 +144,7 @@ public class ImageGenerator {
add("km"); // Khmer add("km"); // Khmer
add("ko"); // Korean add("ko"); // Korean
add("lo"); // Lao add("lo"); // Lao
add("th"); // Thai
add("zh"); // Chinese add("zh"); // Chinese
} }
}; };
@ -154,8 +161,13 @@ public class ImageGenerator {
} }
/** Initailizes the fields of the image image. */ /** Initailizes the fields of the image image. */
public ImageGenerator(int imageWidth, String textName, float fontSize, String fontDirPath) { public ImageGenerator(
mImageWidth = imageWidth; int initialImageWidth,
String textName,
float fontSize,
String fontDirPath,
boolean centerAlignment) {
mImageWidth = initialImageWidth;
mImageHeight = INITIAL_HEIGHT; mImageHeight = INITIAL_HEIGHT;
mVerticalOffset = 0; mVerticalOffset = 0;
@ -165,6 +177,8 @@ public class ImageGenerator {
mTextName = textName; mTextName = textName;
mFontSize = fontSize; mFontSize = fontSize;
mFontDirPath = fontDirPath; mFontDirPath = fontDirPath;
mCenterAlignment = centerAlignment;
} }
/** /**
@ -299,8 +313,6 @@ public class ImageGenerator {
List<String> wrappedText = new ArrayList<>(); List<String> wrappedText = new ArrayList<>();
StringTokenizer st = new StringTokenizer(text, " \n"); StringTokenizer st = new StringTokenizer(text, " \n");
// TODO(xunchang). We assume that all words can fit on the screen. Raise an
// IllegalStateException if the word is wider than the image width.
StringBuilder line = new StringBuilder(); StringBuilder line = new StringBuilder();
while (st.hasMoreTokens()) { while (st.hasMoreTokens()) {
String token = st.nextToken(); String token = st.nextToken();
@ -373,6 +385,43 @@ public class ImageGenerator {
return info; return info;
} }
/** Returns Graphics2D object that uses the given locale. */
private Graphics2D createGraphics(Locale locale) throws IOException, FontFormatException {
Graphics2D graphics = mBufferedImage.createGraphics();
graphics.setColor(Color.WHITE);
graphics.setRenderingHint(
RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
graphics.setFont(loadFontsByLocale(locale.getLanguage()));
return graphics;
}
/** Returns the maximum screen width needed to fit the given text after wrapping. */
private int measureTextWidth(String text, Locale locale)
throws IOException, FontFormatException {
Graphics2D graphics = createGraphics(locale);
FontMetrics fontMetrics = graphics.getFontMetrics();
List<String> wrappedText = wrapText(text, fontMetrics, locale.getLanguage());
int textWidth = 0;
for (String line : wrappedText) {
textWidth = Math.max(textWidth, fontMetrics.stringWidth(line));
}
// This may happen if one single word is larger than the image width.
if (textWidth > mImageWidth) {
throw new IllegalStateException(
"Wrapped text width "
+ textWidth
+ " is larger than image width "
+ mImageWidth
+ " for locale: "
+ locale);
}
return textWidth;
}
/** /**
* Draws the text string on the canvas for given locale. * Draws the text string on the canvas for given locale.
* *
@ -381,16 +430,11 @@ public class ImageGenerator {
* @throws IOException if we cannot find the corresponding font file for the given locale. * @throws IOException if we cannot find the corresponding font file for the given locale.
* @throws FontFormatException if we failed to load the font file for the given locale. * @throws FontFormatException if we failed to load the font file for the given locale.
*/ */
private void drawText(String text, Locale locale, String languageTag, boolean centralAlignment) private void drawText(String text, Locale locale, String languageTag)
throws IOException, FontFormatException { throws IOException, FontFormatException {
Graphics2D graphics = mBufferedImage.createGraphics();
graphics.setColor(Color.WHITE);
graphics.setRenderingHint(
RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
graphics.setFont(loadFontsByLocale(locale.getLanguage()));
System.out.println("Encoding \"" + locale + "\" as \"" + languageTag + "\": " + text); System.out.println("Encoding \"" + locale + "\" as \"" + languageTag + "\": " + text);
Graphics2D graphics = createGraphics(locale);
FontMetrics fontMetrics = graphics.getFontMetrics(); FontMetrics fontMetrics = graphics.getFontMetrics();
List<String> wrappedText = wrapText(text, fontMetrics, locale.getLanguage()); List<String> wrappedText = wrapText(text, fontMetrics, locale.getLanguage());
@ -402,7 +446,7 @@ public class ImageGenerator {
int lineHeight = fontMetrics.getHeight(); int lineHeight = fontMetrics.getHeight();
// Doubles the height of the image if we are short of space. // Doubles the height of the image if we are short of space.
if (mVerticalOffset + lineHeight >= mImageHeight) { if (mVerticalOffset + lineHeight >= mImageHeight) {
resizeHeight(mImageHeight * 2); resize(mImageWidth, mImageHeight * 2);
} }
// Draws the text at mVerticalOffset and increments the offset with line space. // Draws the text at mVerticalOffset and increments the offset with line space.
@ -410,7 +454,7 @@ public class ImageGenerator {
// Draws from right if it's an RTL language. // Draws from right if it's an RTL language.
int x = int x =
centralAlignment mCenterAlignment
? (mImageWidth - fontMetrics.stringWidth(line)) / 2 ? (mImageWidth - fontMetrics.stringWidth(line)) / 2
: RTL_LANGUAGE.contains(languageTag) : RTL_LANGUAGE.contains(languageTag)
? mImageWidth - fontMetrics.stringWidth(line) ? mImageWidth - fontMetrics.stringWidth(line)
@ -431,18 +475,19 @@ public class ImageGenerator {
} }
/** /**
* Redraws the image with the new height. * Redraws the image with the new width and new height.
* *
* @param width the new width of the image in pixels.
* @param height the new height of the image in pixels. * @param height the new height of the image in pixels.
*/ */
private void resizeHeight(int height) { private void resize(int width, int height) {
BufferedImage resizedImage = BufferedImage resizedImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
new BufferedImage(mImageWidth, height, BufferedImage.TYPE_BYTE_GRAY);
Graphics2D graphic = resizedImage.createGraphics(); Graphics2D graphic = resizedImage.createGraphics();
graphic.drawImage(mBufferedImage, 0, 0, null); graphic.drawImage(mBufferedImage, 0, 0, null);
graphic.dispose(); graphic.dispose();
mBufferedImage = resizedImage; mBufferedImage = resizedImage;
mImageWidth = width;
mImageHeight = height; mImageHeight = height;
} }
@ -458,11 +503,16 @@ public class ImageGenerator {
public void generateImage(Map<Locale, String> localizedTextMap, String outputPath) public void generateImage(Map<Locale, String> localizedTextMap, String outputPath)
throws FontFormatException, IOException { throws FontFormatException, IOException {
Map<String, Integer> languageCount = new TreeMap<>(); Map<String, Integer> languageCount = new TreeMap<>();
int textWidth = 0;
for (Locale locale : localizedTextMap.keySet()) { for (Locale locale : localizedTextMap.keySet()) {
String language = locale.getLanguage(); String language = locale.getLanguage();
languageCount.put(language, languageCount.getOrDefault(language, 0) + 1); languageCount.put(language, languageCount.getOrDefault(language, 0) + 1);
textWidth = Math.max(textWidth, measureTextWidth(localizedTextMap.get(locale), locale));
} }
// Removes the black margins to reduce the size of the image.
resize(textWidth, mImageHeight);
for (Locale locale : localizedTextMap.keySet()) { for (Locale locale : localizedTextMap.keySet()) {
Integer count = languageCount.get(locale.getLanguage()); Integer count = languageCount.get(locale.getLanguage());
// Recovery expects en-US instead of en_US. // Recovery expects en-US instead of en_US.
@ -475,12 +525,10 @@ public class ImageGenerator {
languageCount.put(locale.getLanguage(), count - 1); languageCount.put(locale.getLanguage(), count - 1);
} }
drawText(localizedTextMap.get(locale), locale, languageTag, false); drawText(localizedTextMap.get(locale), locale, languageTag);
} }
// TODO(xunchang) adjust the width to save some space if all texts are smaller than resize(mImageWidth, mVerticalOffset);
// imageWidth.
resizeHeight(mVerticalOffset);
ImageIO.write(mBufferedImage, "png", new File(outputPath)); ImageIO.write(mBufferedImage, "png", new File(outputPath));
} }
@ -528,11 +576,17 @@ public class ImageGenerator {
options.addOption( options.addOption(
OptionBuilder.withLongOpt("output_file") OptionBuilder.withLongOpt("output_file")
.withDescription("Path to the generated image") .withDescription("Path to the generated image.")
.hasArgs(1) .hasArgs(1)
.isRequired() .isRequired()
.create()); .create());
options.addOption(
OptionBuilder.withLongOpt("center_alignment")
.withDescription("Align the text in the center of the screen.")
.hasArg(false)
.create());
return options; return options;
} }
@ -557,7 +611,8 @@ public class ImageGenerator {
imageWidth, imageWidth,
cmd.getOptionValue("text_name"), cmd.getOptionValue("text_name"),
DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE,
cmd.getOptionValue("font_dir")); cmd.getOptionValue("font_dir"),
cmd.hasOption("center_alignment"));
Map<Locale, String> localizedStringMap = Map<Locale, String> localizedStringMap =
imageGenerator.readLocalizedStringFromXmls(cmd.getOptionValue("resource_dir")); imageGenerator.readLocalizedStringFromXmls(cmd.getOptionValue("resource_dir"));