PageRenderTime 74ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/newcode/src/com/prupe/mcpatcher/hd/MipmapHelper.java

https://bitbucket.org/prupe/mcpatcher
Java | 497 lines | 446 code | 50 blank | 1 comment | 95 complexity | da0141a567337dd2c6dae0547cd92a32 MD5 | raw file
  1. package com.prupe.mcpatcher.hd;
  2. import com.prupe.mcpatcher.Config;
  3. import com.prupe.mcpatcher.MCLogger;
  4. import com.prupe.mcpatcher.MCPatcherUtils;
  5. import com.prupe.mcpatcher.mal.resource.GLAPI;
  6. import com.prupe.mcpatcher.mal.resource.PropertiesFile;
  7. import com.prupe.mcpatcher.mal.resource.TexturePackAPI;
  8. import com.prupe.mcpatcher.mal.tile.IconAPI;
  9. import net.minecraft.src.ResourceLocation;
  10. import net.minecraft.src.TextureAtlasSprite;
  11. import org.lwjgl.opengl.*;
  12. import org.lwjgl.util.glu.GLU;
  13. import java.awt.*;
  14. import java.awt.image.BufferedImage;
  15. import java.awt.image.DataBuffer;
  16. import java.awt.image.DataBufferByte;
  17. import java.awt.image.DataBufferInt;
  18. import java.lang.ref.Reference;
  19. import java.lang.ref.SoftReference;
  20. import java.math.BigInteger;
  21. import java.nio.ByteBuffer;
  22. import java.nio.ByteOrder;
  23. import java.nio.IntBuffer;
  24. import java.util.ArrayList;
  25. import java.util.Arrays;
  26. import java.util.HashMap;
  27. import java.util.Map;
  28. public class MipmapHelper {
  29. private static final MCLogger logger = MCLogger.getLogger(MCPatcherUtils.MIPMAP);
  30. private static final ResourceLocation MIPMAP_PROPERTIES = TexturePackAPI.newMCPatcherResourceLocation("mipmap.properties");
  31. static final int TEX_FORMAT = GL12.GL_BGRA;
  32. static final int TEX_DATA_TYPE = GL12.GL_UNSIGNED_INT_8_8_8_8_REV;
  33. private static final int MIN_ALPHA = 0x1a;
  34. private static final int MAX_ALPHA = 0xe5;
  35. private static final boolean mipmapSupported;
  36. static final boolean mipmapEnabled = Config.getBoolean(MCPatcherUtils.EXTENDED_HD, "mipmap", false);
  37. static final int maxMipmapLevel = Config.getInt(MCPatcherUtils.EXTENDED_HD, "maxMipmapLevel", 3);
  38. private static final boolean useMipmap;
  39. private static final int mipmapAlignment = (1 << Config.getInt(MCPatcherUtils.EXTENDED_HD, "mipmapAlignment", 3)) - 1;
  40. private static final boolean anisoSupported;
  41. static final int anisoLevel;
  42. private static final int anisoMax;
  43. private static final boolean lodSupported;
  44. private static final int lodBias;
  45. private static final Map<String, Reference<BufferedImage>> imagePool = new HashMap<String, Reference<BufferedImage>>();
  46. private static final Map<Integer, Reference<ByteBuffer>> bufferPool = new HashMap<Integer, Reference<ByteBuffer>>();
  47. private static final Map<String, Boolean> mipmapType = new HashMap<String, Boolean>();
  48. static {
  49. mipmapSupported = GLContext.getCapabilities().OpenGL12;
  50. useMipmap = mipmapSupported && mipmapEnabled && maxMipmapLevel > 0;
  51. anisoSupported = GLContext.getCapabilities().GL_EXT_texture_filter_anisotropic;
  52. if (anisoSupported) {
  53. anisoMax = (int) GL11.glGetFloat(EXTTextureFilterAnisotropic.GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT);
  54. checkGLError("glGetFloat(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT)");
  55. anisoLevel = Math.max(Math.min(Config.getInt(MCPatcherUtils.EXTENDED_HD, "anisotropicFiltering", 1), anisoMax), 1);
  56. } else {
  57. anisoMax = anisoLevel = 1;
  58. }
  59. lodSupported = GLContext.getCapabilities().GL_EXT_texture_lod_bias;
  60. if (lodSupported) {
  61. lodBias = Config.getInt(MCPatcherUtils.EXTENDED_HD, "lodBias", 0);
  62. } else {
  63. lodBias = 0;
  64. }
  65. logger.config("mipmap: supported=%s, enabled=%s, level=%d", mipmapSupported, mipmapEnabled, maxMipmapLevel);
  66. logger.config("anisotropic: supported=%s, level=%d, max=%d", anisoSupported, anisoLevel, anisoMax);
  67. logger.config("lod bias: supported=%s, bias=%d", lodSupported, lodBias);
  68. }
  69. static void setupTexture(int width, int height, boolean blur, boolean clamp, String textureName) {
  70. int mipmaps = useMipmapsForTexture(textureName) ? getMipmapLevels(width, height, 1) : 0;
  71. logger.finer("setupTexture(%s) %dx%d %d mipmaps", textureName, width, height, mipmaps);
  72. int magFilter = blur ? GL11.GL_LINEAR : GL11.GL_NEAREST;
  73. int minFilter = mipmaps > 0 ? GL11.GL_NEAREST_MIPMAP_LINEAR : magFilter;
  74. int wrap = clamp ? GL11.GL_CLAMP : GL11.GL_REPEAT;
  75. if (mipmaps > 0) {
  76. GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, mipmaps);
  77. checkGLError("%s: set GL_TEXTURE_MAX_LEVEL = %d", textureName, mipmaps);
  78. if (anisoSupported && anisoLevel > 1) {
  79. GL11.glTexParameterf(GL11.GL_TEXTURE_2D, EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, anisoLevel);
  80. checkGLError("%s: set GL_TEXTURE_MAX_ANISOTROPY_EXT = %f", textureName, anisoLevel);
  81. }
  82. if (lodSupported) {
  83. GL11.glTexEnvi(EXTTextureLODBias.GL_TEXTURE_FILTER_CONTROL_EXT, EXTTextureLODBias.GL_TEXTURE_LOD_BIAS_EXT, lodBias);
  84. checkGLError("%s: set GL_TEXTURE_LOD_BIAS_EXT = %d", textureName, lodBias);
  85. }
  86. }
  87. GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, minFilter);
  88. GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, magFilter);
  89. GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, wrap);
  90. GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, wrap);
  91. for (int level = 0; level <= mipmaps; level++) {
  92. GL11.glTexImage2D(GL11.GL_TEXTURE_2D, level, GL11.GL_RGBA, width, height, 0, TEX_FORMAT, TEX_DATA_TYPE, (IntBuffer) null);
  93. checkGLError("%s: glTexImage2D %dx%d level %d", textureName, width, height, level);
  94. width >>= 1;
  95. height >>= 1;
  96. }
  97. }
  98. public static void setupTexture(int[] rgb, int width, int height, int x, int y, boolean blur, boolean clamp, String textureName) {
  99. setupTexture(width, height, blur, clamp, textureName);
  100. copySubTexture(rgb, width, height, x, y, textureName);
  101. }
  102. public static int setupTexture(int glTexture, BufferedImage image, boolean blur, boolean clamp, ResourceLocation textureName) {
  103. int width = image.getWidth();
  104. int height = image.getHeight();
  105. GLAPI.glBindTexture(glTexture);
  106. logger.finer("setupTexture(%s, %d, %dx%d, %s, %s)", textureName, glTexture, width, height, blur, clamp);
  107. int[] rgb = new int[width * height];
  108. image.getRGB(0, 0, width, height, rgb, 0, width);
  109. setupTexture(rgb, width, height, 0, 0, blur, clamp, textureName.getPath());
  110. return glTexture;
  111. }
  112. public static void setupTexture(ByteBuffer rgb, int width, int height) {
  113. IntBuffer buffer = rgb.asIntBuffer();
  114. int mipmaps = getMipmapLevelsForCurrentTexture();
  115. for (int level = 0; ; level++) {
  116. GL11.glTexImage2D(GL11.GL_TEXTURE_2D, level, GL11.GL_RGBA, width, height, 0, TEX_FORMAT, TEX_DATA_TYPE, buffer);
  117. if (level >= mipmaps) {
  118. break;
  119. }
  120. IntBuffer newBuffer = getPooledBuffer(width * height).asIntBuffer();
  121. scaleHalf(buffer, width, height, newBuffer, 0);
  122. buffer = newBuffer;
  123. width >>= 1;
  124. height >>= 1;
  125. }
  126. }
  127. public static void setupTexture(int glTexture, int width, int height, String textureName) {
  128. GLAPI.glBindTexture(glTexture);
  129. logger.finer("setupTexture(tilesheet %s, %d, %dx%d)", textureName, glTexture, width, height);
  130. setupTexture(width, height, false, false, textureName);
  131. }
  132. public static void copySubTexture(int[] rgb, int width, int height, int x, int y, String textureName) {
  133. if (rgb == null) {
  134. logger.error("copySubTexture %s %d,%d %dx%d: rgb data is null", textureName, x, y, width, height);
  135. return;
  136. }
  137. IntBuffer buffer = getPooledBuffer(width * height * 4).asIntBuffer();
  138. buffer.put(rgb).position(0);
  139. int mipmaps = getMipmapLevelsForCurrentTexture();
  140. IntBuffer newBuffer;
  141. logger.finest("copySubTexture %s %d,%d %dx%d %d mipmaps", textureName, x, y, width, height, mipmaps);
  142. for (int level = 0; ; ) {
  143. if (width <= 0 || height <= 0) {
  144. break;
  145. }
  146. GL11.glTexSubImage2D(GL11.GL_TEXTURE_2D, level, x, y, width, height, TEX_FORMAT, TEX_DATA_TYPE, buffer);
  147. checkGLError("%s: glTexSubImage2D(%d, %d, %d, %d, %d)", textureName, level, x, y, width, height);
  148. if (level >= mipmaps) {
  149. break;
  150. }
  151. newBuffer = getPooledBuffer(width * height).asIntBuffer();
  152. scaleHalf(buffer, width, height, newBuffer, 0);
  153. buffer = newBuffer;
  154. level++;
  155. x >>= 1;
  156. y >>= 1;
  157. width >>= 1;
  158. height >>= 1;
  159. }
  160. }
  161. public static void copySubTexture(TextureAtlasSprite texture, int index) {
  162. int width = IconAPI.getIconWidth(texture);
  163. int height = IconAPI.getIconHeight(texture);
  164. if (texture.mipmaps == null || texture.mipmaps.size() != texture.animationFrames.size()) {
  165. texture.mipmaps = new ArrayList<IntBuffer[]>(texture.animationFrames.size());
  166. int mipmaps = getMipmapLevelsForCurrentTexture();
  167. if (mipmaps > 0) {
  168. logger.fine("generating %d mipmaps for tile %s", mipmaps, IconAPI.getIconName(texture));
  169. }
  170. for (int i = 0; i < texture.animationFrames.size(); i++) {
  171. texture.mipmaps.add(generateMipmaps(texture.animationFrames.get(i), width, height, mipmaps));
  172. }
  173. }
  174. int x = IconAPI.getIconX0(texture);
  175. int y = IconAPI.getIconY0(texture);
  176. IntBuffer[] mipmapData = texture.mipmaps.get(index);
  177. if (mipmapData == null) {
  178. return;
  179. }
  180. for (int level = 0; level < mipmapData.length; level++) {
  181. GL11.glTexSubImage2D(GL11.GL_TEXTURE_2D, level, x, y, width, height, TEX_FORMAT, TEX_DATA_TYPE, mipmapData[level]);
  182. x >>= 1;
  183. y >>= 1;
  184. width >>= 1;
  185. height >>= 1;
  186. }
  187. }
  188. private static IntBuffer[] generateMipmaps(int[] rgb, int width, int height, int mipmaps) {
  189. if (rgb == null) {
  190. return null;
  191. }
  192. ArrayList<IntBuffer> mipmapData = new ArrayList<IntBuffer>();
  193. IntBuffer buffer = newIntBuffer(width * height * 4);
  194. buffer.put(rgb).position(0);
  195. IntBuffer newBuffer;
  196. for (int level = 0; ; ) {
  197. mipmapData.add(buffer);
  198. if (width <= 0 || height <= 0 || level >= mipmaps) {
  199. break;
  200. }
  201. newBuffer = newIntBuffer(width * height);
  202. scaleHalf(buffer, width, height, newBuffer, 0);
  203. buffer = newBuffer;
  204. level++;
  205. width >>= 1;
  206. height >>= 1;
  207. }
  208. return mipmapData.toArray(new IntBuffer[mipmapData.size()]);
  209. }
  210. static BufferedImage fixTransparency(ResourceLocation name, BufferedImage image) {
  211. if (image == null) {
  212. return image;
  213. }
  214. long s1 = System.currentTimeMillis();
  215. image = convertToARGB(image);
  216. int width = image.getWidth();
  217. int height = image.getHeight();
  218. IntBuffer buffer = getImageAsARGBIntBuffer(image);
  219. IntBuffer scaledBuffer = buffer;
  220. outer:
  221. while (width % 2 == 0 && height % 2 == 0) {
  222. for (int i = 0; i < scaledBuffer.limit(); i++) {
  223. if (scaledBuffer.get(i) >>> 24 == 0) {
  224. IntBuffer newBuffer = getPooledBuffer(width * height).asIntBuffer();
  225. scaleHalf(scaledBuffer, width, height, newBuffer, 8);
  226. scaledBuffer = newBuffer;
  227. width >>= 1;
  228. height >>= 1;
  229. continue outer;
  230. }
  231. }
  232. break;
  233. }
  234. long s2 = System.currentTimeMillis();
  235. if (scaledBuffer != buffer) {
  236. setBackgroundColor(buffer, image.getWidth(), image.getHeight(), scaledBuffer, image.getWidth() / width);
  237. }
  238. long s3 = System.currentTimeMillis();
  239. logger.finest("bg fix (tile %s): scaling %dms, setbg %dms", name, s2 - s1, s3 - s2);
  240. return image;
  241. }
  242. static void reset() {
  243. mipmapType.clear();
  244. mipmapType.put("terrain", true);
  245. mipmapType.put("items", false);
  246. PropertiesFile properties = PropertiesFile.get(logger, MIPMAP_PROPERTIES);
  247. if (properties != null) {
  248. for (Map.Entry<String, String> entry : properties.entrySet()) {
  249. String key = entry.getKey().trim();
  250. boolean value = Boolean.parseBoolean(entry.getValue().trim().toLowerCase());
  251. if (key.endsWith(".png")) {
  252. mipmapType.put(key, value);
  253. }
  254. }
  255. }
  256. }
  257. static boolean useMipmapsForTexture(String texture) {
  258. if (!useMipmap || texture == null) {
  259. return false;
  260. } else if (mipmapType.containsKey(texture)) {
  261. return mipmapType.get(texture);
  262. } else if (texture.contains("item") ||
  263. texture.startsWith("textures/colormap/") ||
  264. texture.startsWith("textures/environment/") ||
  265. texture.startsWith("textures/font/") ||
  266. texture.startsWith("textures/gui/") ||
  267. texture.startsWith("textures/map/") ||
  268. texture.startsWith("textures/misc/") ||
  269. texture.startsWith(TexturePackAPI.MCPATCHER_SUBDIR + "colormap/") ||
  270. texture.startsWith(TexturePackAPI.MCPATCHER_SUBDIR + "cit/") ||
  271. texture.startsWith(TexturePackAPI.MCPATCHER_SUBDIR + "dial/") ||
  272. texture.startsWith(TexturePackAPI.MCPATCHER_SUBDIR + "font/") ||
  273. texture.startsWith(TexturePackAPI.MCPATCHER_SUBDIR + "lightmap/") ||
  274. texture.startsWith(TexturePackAPI.MCPATCHER_SUBDIR + "sky/") ||
  275. // 1.5 stuff
  276. texture.startsWith("%") ||
  277. texture.startsWith("##") ||
  278. texture.startsWith("/achievement/") ||
  279. texture.startsWith("/environment/") ||
  280. texture.startsWith("/font/") ||
  281. texture.startsWith("/gui/") ||
  282. texture.startsWith("/misc/") ||
  283. texture.startsWith("/terrain/") ||
  284. texture.startsWith("/title/")) {
  285. return false;
  286. } else {
  287. return true;
  288. }
  289. }
  290. static int getMipmapLevelsForCurrentTexture() {
  291. int filter = GL11.glGetTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER);
  292. if (filter != GL11.GL_NEAREST_MIPMAP_LINEAR && filter != GL11.GL_NEAREST_MIPMAP_NEAREST) {
  293. return 0;
  294. }
  295. return Math.min(maxMipmapLevel, GL11.glGetTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL));
  296. }
  297. private static int gcd(int a, int b) {
  298. return BigInteger.valueOf(a).gcd(BigInteger.valueOf(b)).intValue();
  299. }
  300. private static int getMipmapLevels(int width, int height, int minSize) {
  301. int size = gcd(width, height);
  302. int mipmap;
  303. for (mipmap = 0; size >= minSize && ((size & 1) == 0) && mipmap < maxMipmapLevel; size >>= 1, mipmap++) {
  304. }
  305. return mipmap;
  306. }
  307. private static BufferedImage getPooledImage(int width, int height, int index) {
  308. String key = String.format("%dx%d#%d", width, height, index);
  309. Reference<BufferedImage> ref = imagePool.get(key);
  310. BufferedImage image = (ref == null ? null : ref.get());
  311. if (image == null) {
  312. image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
  313. imagePool.put(key, new SoftReference<BufferedImage>(image));
  314. }
  315. return image;
  316. }
  317. static IntBuffer newIntBuffer(int size) {
  318. ByteBuffer buffer = ByteBuffer.allocateDirect(size);
  319. buffer.order(ByteOrder.LITTLE_ENDIAN);
  320. return buffer.asIntBuffer();
  321. }
  322. private static ByteBuffer getPooledBuffer(int size) {
  323. Reference<ByteBuffer> ref = bufferPool.get(size);
  324. ByteBuffer buffer = (ref == null ? null : ref.get());
  325. if (buffer == null) {
  326. buffer = ByteBuffer.allocateDirect(size);
  327. bufferPool.put(size, new SoftReference<ByteBuffer>(buffer));
  328. }
  329. buffer.order(ByteOrder.LITTLE_ENDIAN);
  330. buffer.position(0);
  331. return buffer;
  332. }
  333. private static BufferedImage convertToARGB(BufferedImage image) {
  334. if (image == null) {
  335. return null;
  336. } else if (image.getType() == BufferedImage.TYPE_INT_ARGB) {
  337. return image;
  338. } else {
  339. int width = image.getWidth();
  340. int height = image.getHeight();
  341. logger.finest("converting %dx%d image to ARGB", width, height);
  342. BufferedImage newImage = getPooledImage(width, height, 0);
  343. Graphics2D graphics = newImage.createGraphics();
  344. Arrays.fill(getImageAsARGBIntBuffer(newImage).array(), 0);
  345. graphics.drawImage(image, 0, 0, null);
  346. return newImage;
  347. }
  348. }
  349. private static IntBuffer getImageAsARGBIntBuffer(BufferedImage image) {
  350. DataBuffer buffer = image.getRaster().getDataBuffer();
  351. if (buffer instanceof DataBufferInt) {
  352. return IntBuffer.wrap(((DataBufferInt) buffer).getData());
  353. } else if (buffer instanceof DataBufferByte) {
  354. return ByteBuffer.wrap(((DataBufferByte) buffer).getData()).order(ByteOrder.BIG_ENDIAN).asIntBuffer();
  355. } else {
  356. int width = image.getWidth();
  357. int height = image.getHeight();
  358. int[] pixels = new int[width * height];
  359. image.getRGB(0, 0, width, height, pixels, 0, width);
  360. return IntBuffer.wrap(pixels);
  361. }
  362. }
  363. private static void setBackgroundColor(IntBuffer buffer, int width, int height, IntBuffer scaledBuffer, int scale) {
  364. for (int i = 0; i < width; i++) {
  365. for (int j = 0; j < height; j++) {
  366. int k = width * j + i;
  367. int pixel = buffer.get(k);
  368. if ((pixel & 0xff000000) == 0) {
  369. pixel = scaledBuffer.get((j / scale) * (width / scale) + i / scale);
  370. buffer.put(k, pixel & 0x00ffffff);
  371. }
  372. }
  373. }
  374. }
  375. static void scaleHalf(IntBuffer in, int w, int h, IntBuffer out, int rotate) {
  376. for (int i = 0; i < w / 2; i++) {
  377. for (int j = 0; j < h / 2; j++) {
  378. int k = w * 2 * j + 2 * i;
  379. int pixel00 = in.get(k);
  380. int pixel01 = in.get(k + 1);
  381. int pixel10 = in.get(k + w);
  382. int pixel11 = in.get(k + w + 1);
  383. if (rotate != 0) {
  384. pixel00 = Integer.rotateLeft(pixel00, rotate);
  385. pixel01 = Integer.rotateLeft(pixel01, rotate);
  386. pixel10 = Integer.rotateLeft(pixel10, rotate);
  387. pixel11 = Integer.rotateLeft(pixel11, rotate);
  388. }
  389. int pixel = average4RGBA(pixel00, pixel01, pixel10, pixel11);
  390. if (rotate != 0) {
  391. pixel = Integer.rotateRight(pixel, rotate);
  392. }
  393. out.put(w / 2 * j + i, pixel);
  394. }
  395. }
  396. }
  397. private static int average4RGBA(int pixel00, int pixel01, int pixel10, int pixel11) {
  398. int a00 = pixel00 & 0xff;
  399. int a01 = pixel01 & 0xff;
  400. int a10 = pixel10 & 0xff;
  401. int a11 = pixel11 & 0xff;
  402. switch ((a00 << 24) | (a01 << 16) | (a10 << 8) | a11) {
  403. case 0xff000000:
  404. return pixel00;
  405. case 0x00ff0000:
  406. return pixel01;
  407. case 0x0000ff00:
  408. return pixel10;
  409. case 0x000000ff:
  410. return pixel11;
  411. case 0xffff0000:
  412. return average2RGBA(pixel00, pixel01);
  413. case 0xff00ff00:
  414. return average2RGBA(pixel00, pixel10);
  415. case 0xff0000ff:
  416. return average2RGBA(pixel00, pixel11);
  417. case 0x00ffff00:
  418. return average2RGBA(pixel01, pixel10);
  419. case 0x00ff00ff:
  420. return average2RGBA(pixel01, pixel11);
  421. case 0x0000ffff:
  422. return average2RGBA(pixel10, pixel11);
  423. case 0x00000000:
  424. case 0xffffffff:
  425. return average2RGBA(average2RGBA(pixel00, pixel11), average2RGBA(pixel01, pixel10));
  426. default:
  427. int a = a00 + a01 + a10 + a11;
  428. int pixel = a >> 2;
  429. for (int i = 8; i < 32; i += 8) {
  430. int average = (a00 * ((pixel00 >> i) & 0xff) + a01 * ((pixel01 >> i) & 0xff) +
  431. a10 * ((pixel10 >> i) & 0xff) + a11 * ((pixel11 >> i) & 0xff)) / a;
  432. pixel |= (average << i);
  433. }
  434. return pixel;
  435. }
  436. }
  437. private static int average2RGBA(int a, int b) {
  438. return (((a & 0xfefefefe) >>> 1) + ((b & 0xfefefefe) >>> 1)) | (a & b & 0x01010101);
  439. }
  440. private static void checkGLError(String format, Object... params) {
  441. int error = GL11.glGetError();
  442. if (error != 0) {
  443. String message = GLU.gluErrorString(error) + ": " + String.format(format, params);
  444. new RuntimeException(message).printStackTrace();
  445. }
  446. }
  447. }