private final void shadowColor(Color c, int lightlevel, int[] shadowscale) {
   int scale = shadowscale[lightlevel];
   if (scale < 256)
     c.setRGBA(
         (c.getRed() * scale) >> 8,
         (c.getGreen() * scale) >> 8,
         (c.getBlue() * scale) >> 8,
         c.getAlpha());
 }
 private void doScaleWithBilinear(int[] argb, int[] zargb, int width, int height) {
   Color c1 = new Color();
   /* Now, compute zoomed tile - bilinear filter 2x2 -> 1x1 */
   for (int y = 0; y < height; y += 2) {
     for (int x = 0; x < width; x += 2) {
       int red = 0;
       int green = 0;
       int blue = 0;
       int alpha = 0;
       for (int yy = y; yy < y + 2; yy++) {
         for (int xx = x; xx < x + 2; xx++) {
           c1.setARGB(argb[(yy * width) + xx]);
           red += c1.getRed();
           green += c1.getGreen();
           blue += c1.getBlue();
           alpha += c1.getAlpha();
         }
       }
       c1.setRGBA(red >> 2, green >> 2, blue >> 2, alpha >> 2);
       zargb[(y * width / 4) + (x / 2)] = c1.getARGB();
     }
   }
 }
  private void applySmoothLighting(
      HDPerspectiveState ps, HDShaderState ss, Color incolor, Color[] outcolor, int[] shadowscale) {
    int[] xyz = ps.getSubblockCoord();
    int scale = (int) ps.getScale();
    int mid = scale / 2;
    BlockStep s1, s2;
    int w1, w2;
    /* Figure out which directions to look */
    switch (ps.getLastBlockStep()) {
      case X_MINUS:
      case X_PLUS:
        if (xyz[1] < mid) {
          s1 = BlockStep.Y_MINUS;
          w1 = mid - xyz[1];
        } else {
          s1 = BlockStep.Y_PLUS;
          w1 = xyz[1] - mid;
        }
        if (xyz[2] < mid) {
          s2 = BlockStep.Z_MINUS;
          w2 = mid - xyz[2];
        } else {
          s2 = BlockStep.Z_PLUS;
          w2 = xyz[2] - mid;
        }
        break;
      case Z_MINUS:
      case Z_PLUS:
        if (xyz[0] < mid) {
          s1 = BlockStep.X_MINUS;
          w1 = mid - xyz[0];
        } else {
          s1 = BlockStep.X_PLUS;
          w1 = xyz[0] - mid;
        }
        if (xyz[1] < mid) {
          s2 = BlockStep.Y_MINUS;
          w2 = mid - xyz[1];
        } else {
          s2 = BlockStep.Y_PLUS;
          w2 = xyz[1] - mid;
        }
        break;
      default:
        if (xyz[0] < mid) {
          s1 = BlockStep.X_MINUS;
          w1 = mid - xyz[0];
        } else {
          s1 = BlockStep.X_PLUS;
          w1 = xyz[0] - mid;
        }
        if (xyz[2] < mid) {
          s2 = BlockStep.Z_MINUS;
          w2 = mid - xyz[2];
        } else {
          s2 = BlockStep.Z_PLUS;
          w2 = xyz[2] - mid;
        }
        break;
    }
    /* Now get the 3 needed light levels */
    LightLevels skyemit0 = ps.getCachedLightLevels(0);
    ps.getLightLevels(skyemit0);
    LightLevels skyemit1 = ps.getCachedLightLevels(1);
    ps.getLightLevelsAtStep(s1, skyemit1);
    LightLevels skyemit2 = ps.getCachedLightLevels(2);
    ps.getLightLevelsAtStep(s2, skyemit2);

    /* Get light levels */
    int ll0 = getLightLevel(skyemit0, true);
    int ll1 = getLightLevel(skyemit1, true);
    int weight = 0;
    if (ll1 < ll0) weight -= w1;
    else if (ll1 > ll0) weight += w1;
    int ll2 = getLightLevel(skyemit2, true);
    if (ll2 < ll0) weight -= w2;
    else if (ll2 > ll0) weight += w2;
    outcolor[0].setColor(incolor);
    int cscale = 256;
    if (weight == 0) {
      cscale = shadowscale[ll0];
    } else if (weight < 0) {
        /* If negative, interpolate down */
      weight = -weight;
      if (ll0 > 0) {
        cscale = (shadowscale[ll0] * (scale - weight) + shadowscale[ll0 - 1] * weight) / scale;
      } else {
        cscale = shadowscale[ll0];
      }
    } else {
      if (ll0 < 15) {
        cscale = (shadowscale[ll0] * (scale - weight) + shadowscale[ll0 + 1] * weight) / scale;
      } else {
        cscale = shadowscale[ll0];
      }
    }
    if (cscale < 256) {
      Color c = outcolor[0];
      c.setRGBA(
          (c.getRed() * cscale) >> 8,
          (c.getGreen() * cscale) >> 8,
          (c.getBlue() * cscale) >> 8,
          c.getAlpha());
    }
    if (outcolor.length > 1) {
      ll0 = getLightLevel(skyemit0, false);
      ll1 = getLightLevel(skyemit1, false);
      weight = 0;
      if (ll1 < ll0) weight -= w1;
      else if (ll1 > ll0) weight += w1;
      ll2 = getLightLevel(skyemit2, false);
      if (ll2 < ll0) weight -= w2;
      else if (ll2 > ll0) weight += w2;
      outcolor[1].setColor(incolor);
      cscale = 256;
      if (weight == 0) {
        cscale = shadowscale[ll0];
      } else if (weight < 0) {
          /* If negative, interpolate down */
        weight = -weight;
        if (ll0 > 0) {
          cscale = (shadowscale[ll0] * (scale - weight) + shadowscale[ll0 - 1] * weight) / scale;
        } else {
          cscale = shadowscale[ll0];
        }
      } else {
        if (ll0 < 15) {
          cscale = (shadowscale[ll0] * (scale - weight) + shadowscale[ll0 + 1] * weight) / scale;
        } else {
          cscale = shadowscale[ll0];
        }
      }
      if (cscale < 256) {
        Color c = outcolor[1];
        c.setRGBA(
            (c.getRed() * cscale) >> 8,
            (c.getGreen() * cscale) >> 8,
            (c.getBlue() * cscale) >> 8,
            c.getAlpha());
      }
    }
  }
  public boolean render(MapChunkCache cache, KzedMapTile tile, File outputFile) {
    World world = tile.getWorld();
    boolean isnether = (world.getEnvironment() == Environment.NETHER);
    KzedBufferedImage im = KzedMap.allocateBufferedImage(KzedMap.tileWidth, KzedMap.tileHeight);
    KzedBufferedImage zim =
        KzedMap.allocateBufferedImage(KzedMap.tileWidth / 2, KzedMap.tileHeight / 2);
    boolean isempty = true;

    KzedBufferedImage im_day = null;
    KzedBufferedImage zim_day = null;
    if (night_and_day) {
      im_day = KzedMap.allocateBufferedImage(KzedMap.tileWidth, KzedMap.tileHeight);
      zim_day = KzedMap.allocateBufferedImage(KzedMap.tileWidth / 2, KzedMap.tileHeight / 2);
    }

    int ix = KzedMap.anchorx + tile.px / 2 + tile.py / 2 - ((127 - maximumHeight) / 2);
    int iy = maximumHeight;
    int iz = KzedMap.anchorz + tile.px / 2 - tile.py / 2 + ((127 - maximumHeight) / 2);

    /* Don't mess with existing height-clipped renders */
    if (maximumHeight < 127) isnether = false;

    int jx, jz;

    int x, y;

    MapChunkCache.MapIterator mapiter = cache.getIterator(ix, iy, iz);

    Color c1 = new Color();
    Color c2 = new Color();
    int[] argb = im.argb_buf;
    int[] zargb = zim.argb_buf;
    Color c1_day = null;
    Color c2_day = null;
    int[] argb_day = null;
    int[] zargb_day = null;
    if (night_and_day) {
      c1_day = new Color();
      c2_day = new Color();
      argb_day = im_day.argb_buf;
      zargb_day = zim_day.argb_buf;
    }
    int rowoff = 0;
    /* draw the map */
    for (y = 0; y < KzedMap.tileHeight; ) {
      jx = ix;
      jz = iz;

      for (x = KzedMap.tileWidth - 1; x >= 0; x -= 2) {
        mapiter.initialize(jx, iy, jz);
        scan(world, 0, isnether, c1, c1_day, mapiter);
        mapiter.initialize(jx, iy, jz);
        scan(world, 2, isnether, c2, c2_day, mapiter);

        argb[rowoff + x] = c1.getARGB();
        argb[rowoff + x - 1] = c2.getARGB();

        if (night_and_day) {
          argb_day[rowoff + x] = c1_day.getARGB();
          argb_day[rowoff + x - 1] = c2_day.getARGB();
        }

        isempty = isempty && c1.isTransparent() && c2.isTransparent();

        jx++;
        jz++;
      }

      y++;
      rowoff += KzedMap.tileWidth;

      jx = ix;
      jz = iz - 1;

      for (x = KzedMap.tileWidth - 1; x >= 0; x -= 2) {
        mapiter.initialize(jx, iy, jz);
        scan(world, 2, isnether, c1, c1_day, mapiter);
        jx++;
        jz++;
        mapiter.initialize(jx, iy, jz);
        scan(world, 0, isnether, c2, c2_day, mapiter);

        argb[rowoff + x] = c1.getARGB();
        argb[rowoff + x - 1] = c2.getARGB();

        if (night_and_day) {
          argb_day[rowoff + x] = c1_day.getARGB();
          argb_day[rowoff + x - 1] = c2_day.getARGB();
        }

        isempty = isempty && c1.isTransparent() && c2.isTransparent();
      }
      y++;
      rowoff += KzedMap.tileWidth;

      ix++;
      iz--;
    }
    /* Now, compute zoomed tile - bilinear filter 2x2 -> 1x1 */
    doScaleWithBilinear(argb, zargb, KzedMap.tileWidth, KzedMap.tileHeight);
    if (night_and_day) {
      doScaleWithBilinear(argb_day, zargb_day, KzedMap.tileWidth, KzedMap.tileHeight);
    }

    /* Hand encoding and writing file off to MapManager */
    KzedZoomedMapTile zmtile =
        new KzedZoomedMapTile(tile.getWorld(), (KzedMap) tile.getMap(), tile);
    File zoomFile = MapManager.mapman.getTileFile(zmtile);

    doFileWrites(outputFile, tile, im, im_day, zmtile, zoomFile, zim, zim_day, !isempty);

    return !isempty;
  }
  protected void scan(
      World world,
      int seq,
      boolean isnether,
      final Color result,
      final Color result_day,
      MapChunkCache.MapIterator mapiter) {
    int lightlevel = 15;
    int lightlevel_day = 15;
    result.setTransparent();
    if (result_day != null) result_day.setTransparent();
    for (; ; ) {
      if (mapiter.y < 0) {
        return;
      }
      int id = mapiter.getBlockTypeID();
      int data = 0;
      if (isnether) {
          /* Make bedrock ceiling into air in nether */
        if (id != 0) {
          /* Remember first color we see, in case we wind up solid */
          if (result.isTransparent())
            if (colorScheme.colors[id] != null) result.setColor(colorScheme.colors[id][seq]);
          id = 0;
        } else isnether = false;
      }
      if (id != 0) {
          /* No update needed for air */
        if (colorScheme.datacolors[id] != null) {
            /* If data colored */
          data = mapiter.getBlockData();
        }
        if ((shadowscale != null) && (mapiter.y < 127)) {
          /* Find light level of previous chunk */
          switch (seq) {
            case 0:
            case 2:
              mapiter.incrementY();
              break;
            case 1:
              mapiter.incrementX();
              break;
            case 3:
              mapiter.decrementZ();
              break;
          }
          lightlevel = lightlevel_day = mapiter.getBlockSkyLight();
          if (lightscale != null) lightlevel = lightscale[lightlevel];
          if ((lightlevel < 15) || (lightlevel_day < 15)) {
            int emitted = mapiter.getBlockEmittedLight();
            lightlevel = Math.max(emitted, lightlevel);
            lightlevel_day = Math.max(emitted, lightlevel_day);
          }
          switch (seq) {
            case 0:
            case 2:
              mapiter.decrementY();
              break;
            case 1:
              mapiter.decrementX();
              break;
            case 3:
              mapiter.incrementZ();
              break;
          }
        }
      }

      switch (seq) {
        case 0:
          mapiter.decrementX();
          break;
        case 1:
        case 3:
          mapiter.decrementY();
          break;
        case 2:
          mapiter.incrementZ();
          break;
      }

      seq = (seq + 1) & 3;

      if (id != 0) {
        if (highlightBlocks.contains(id)) {
          result.setColor(highlightColor);
          return;
        }
        Color[] colors;
        if (data != 0) colors = colorScheme.datacolors[id][data];
        else colors = colorScheme.colors[id];
        if (colors != null) {
          Color c = colors[seq];
          if (c.getAlpha() > 0) {
            /* we found something that isn't transparent, or not doing transparency */
            if ((!transparency) || (c.getAlpha() == 255)) {
              /* it's opaque - the ray ends here */
              result.setARGB(c.getARGB() | 0xFF000000);
              if (lightlevel < 15) {
                  /* Not full light? */
                shadowColor(result, lightlevel);
              }
              if (result_day != null) {
                if (lightlevel_day == lightlevel) /* Same light = same result */
                  result_day.setColor(result);
                else {
                  result_day.setColor(c);
                  if (lightlevel_day < 15) shadowColor(result_day, lightlevel_day);
                }
              }
              return;
            }

            /* this block is transparent, so recurse */
            scan(world, seq, isnether, result, result_day, mapiter);

            int cr = c.getRed();
            int cg = c.getGreen();
            int cb = c.getBlue();
            int ca = c.getAlpha();
            if (lightlevel < 15) {
              int scale = shadowscale[lightlevel];
              cr = (cr * scale) >> 8;
              cg = (cg * scale) >> 8;
              cb = (cb * scale) >> 8;
            }
            cr *= ca;
            cg *= ca;
            cb *= ca;
            int na = 255 - ca;
            result.setRGBA(
                (result.getRed() * na + cr) >> 8,
                (result.getGreen() * na + cg) >> 8,
                (result.getBlue() * na + cb) >> 8,
                255);
            /* Handle day also */
            if (result_day != null) {
              cr = c.getRed();
              cg = c.getGreen();
              cb = c.getBlue();
              if (lightlevel_day < 15) {
                int scale = shadowscale[lightlevel_day];
                cr = (cr * scale) >> 8;
                cg = (cg * scale) >> 8;
                cb = (cb * scale) >> 8;
              }
              cr *= ca;
              cg *= ca;
              cb *= ca;
              result_day.setRGBA(
                  (result_day.getRed() * na + cr) >> 8,
                  (result_day.getGreen() * na + cg) >> 8,
                  (result_day.getBlue() * na + cb) >> 8,
                  255);
            }
            return;
          }
        }
      }
    }
  }
  protected void scan(
      DynmapWorld world,
      int seq,
      boolean isnether,
      final Color result,
      final Color result_day,
      MapIterator mapiter) {
    int lightlevel = 15;
    int lightlevel_day = 15;
    BiomeMap bio = null;
    double rain = 0.0;
    double temp = 0.0;
    result.setTransparent();
    if (result_day != null) result_day.setTransparent();
    for (; ; ) {
      if (mapiter.getY() < 0) {
        return;
      }
      int id = mapiter.getBlockTypeID();
      int data = 0;
      if (isnether) {
          /* Make bedrock ceiling into air in nether */
        if (id != 0) {
          /* Remember first color we see, in case we wind up solid */
          if (result.isTransparent()) {
            try {
              if (colorScheme.colors[id] != null) {
                result.setColor(colorScheme.colors[id][seq]);
              }
            } catch (ArrayIndexOutOfBoundsException aioobx) {
              colorScheme.resizeColorArray(id);
            }
          }
          id = 0;
        } else isnether = false;
      }
      if (id != 0) {
          /* No update needed for air */
        switch (biomecolored) {
          case NONE:
            try {
              if (colorScheme.datacolors[id] != null) {
                  /* If data colored */
                data = mapiter.getBlockData();
              }
            } catch (ArrayIndexOutOfBoundsException aioobx) {
              colorScheme.resizeColorArray(id);
            }
            break;
          case BIOME:
            bio = mapiter.getBiome();
            break;
          case RAINFALL:
            rain = mapiter.getRawBiomeRainfall();
            break;
          case TEMPERATURE:
            temp = mapiter.getRawBiomeTemperature();
            break;
        }
        if ((shadowscale != null) && (mapiter.getY() < (mapiter.getWorldHeight() - 1))) {
          /* Find light level of previous chunk */
          BlockStep last = mapiter.unstepPosition();
          lightlevel = lightlevel_day = mapiter.getBlockSkyLight();
          if (lightscale != null) lightlevel = lightscale[lightlevel];
          if ((lightlevel < 15) || (lightlevel_day < 15)) {
            int emitted = mapiter.getBlockEmittedLight();
            lightlevel = Math.max(emitted, lightlevel);
            lightlevel_day = Math.max(emitted, lightlevel_day);
          }
          mapiter.stepPosition(last);
        }
      }

      switch (seq) {
        case 0:
          mapiter.stepPosition(BlockStep.X_MINUS);
          break;
        case 1:
        case 3:
          mapiter.stepPosition(BlockStep.Y_MINUS);
          break;
        case 2:
          mapiter.stepPosition(BlockStep.Z_PLUS);
          break;
      }

      seq = (seq + 1) & 3;

      if (id != 0) {
        if (highlightBlocks.contains(id)) {
          result.setColor(highlightColor);
          return;
        }
        Color[] colors = null;
        switch (biomecolored) {
          case NONE:
            try {
              if (data != 0) colors = colorScheme.datacolors[id][data];
              else colors = colorScheme.colors[id];
            } catch (ArrayIndexOutOfBoundsException aioobx) {
              colorScheme.resizeColorArray(id);
            }
            break;
          case BIOME:
            if (bio != null) colors = colorScheme.biomecolors[bio.ordinal()];
            break;
          case RAINFALL:
            colors = colorScheme.getRainColor(rain);
            break;
          case TEMPERATURE:
            colors = colorScheme.getTempColor(temp);
            break;
        }
        if (colors != null) {
          Color c = colors[seq];
          if (c.getAlpha() > 0) {
            /* we found something that isn't transparent, or not doing transparency */
            if ((!transparency) || (c.getAlpha() == 255)) {
              /* it's opaque - the ray ends here */
              result.setARGB(c.getARGB() | 0xFF000000);
              if (lightlevel < 15) {
                  /* Not full light? */
                shadowColor(result, lightlevel);
              }
              if (result_day != null) {
                if (lightlevel_day == lightlevel) /* Same light = same result */
                  result_day.setColor(result);
                else {
                  result_day.setColor(c);
                  if (lightlevel_day < 15) shadowColor(result_day, lightlevel_day);
                }
              }
              return;
            }

            /* this block is transparent, so recurse */
            scan(world, seq, isnether, result, result_day, mapiter);

            int cr = c.getRed();
            int cg = c.getGreen();
            int cb = c.getBlue();
            int ca = c.getAlpha();
            if (lightlevel < 15) {
              int scale = shadowscale[lightlevel];
              cr = (cr * scale) >> 8;
              cg = (cg * scale) >> 8;
              cb = (cb * scale) >> 8;
            }
            cr *= ca;
            cg *= ca;
            cb *= ca;
            int na = 255 - ca;
            result.setRGBA(
                (result.getRed() * na + cr) >> 8,
                (result.getGreen() * na + cg) >> 8,
                (result.getBlue() * na + cb) >> 8,
                255);
            /* Handle day also */
            if (result_day != null) {
              cr = c.getRed();
              cg = c.getGreen();
              cb = c.getBlue();
              if (lightlevel_day < 15) {
                int scale = shadowscale[lightlevel_day];
                cr = (cr * scale) >> 8;
                cg = (cg * scale) >> 8;
                cb = (cb * scale) >> 8;
              }
              cr *= ca;
              cg *= ca;
              cb *= ca;
              result_day.setRGBA(
                  (result_day.getRed() * na + cr) >> 8,
                  (result_day.getGreen() * na + cg) >> 8,
                  (result_day.getBlue() * na + cb) >> 8,
                  255);
            }
            return;
          }
        }
      }
    }
  }
  @Override
  public boolean render(MapChunkCache cache, HDMapTile tile, String mapname) {
    Color rslt = new Color();
    MapIterator mapiter = cache.getIterator(0, 0, 0);
    /* Build shader state object for each shader */
    HDShaderState[] shaderstate =
        MapManager.mapman.hdmapman.getShaderStateForTile(tile, cache, mapiter, mapname);
    int numshaders = shaderstate.length;
    if (numshaders == 0) return false;
    /* Check if nether world */
    boolean isnether = tile.getWorld().getEnvironment() == Environment.NETHER;
    /* Create buffered image for each */
    DynmapBufferedImage im[] = new DynmapBufferedImage[numshaders];
    DynmapBufferedImage dayim[] = new DynmapBufferedImage[numshaders];
    int[][] argb_buf = new int[numshaders][];
    int[][] day_argb_buf = new int[numshaders][];

    for (int i = 0; i < numshaders; i++) {
      HDShader shader = shaderstate[i].getShader();
      HDLighting lighting = shaderstate[i].getLighting();
      if (shader.isEmittedLightLevelNeeded() || lighting.isEmittedLightLevelNeeded())
        need_emittedlightlevel = true;
      if (shader.isSkyLightLevelNeeded() || lighting.isSkyLightLevelNeeded())
        need_skylightlevel = true;
      im[i] = DynmapBufferedImage.allocateBufferedImage(tileWidth, tileHeight);
      argb_buf[i] = im[i].argb_buf;
      if (lighting.isNightAndDayEnabled()) {
        dayim[i] = DynmapBufferedImage.allocateBufferedImage(tileWidth, tileHeight);
        day_argb_buf[i] = dayim[i].argb_buf;
      }
    }

    /* Create perspective state object */
    OurPerspectiveState ps = new OurPerspectiveState(mapiter, isnether);

    ps.top = new Vector3D();
    ps.bottom = new Vector3D();
    double xbase = tile.tx * tileWidth;
    double ybase = tile.ty * tileHeight;
    boolean shaderdone[] = new boolean[numshaders];
    boolean rendered[] = new boolean[numshaders];
    for (int x = 0; x < tileWidth; x++) {
      ps.px = x;
      for (int y = 0; y < tileHeight; y++) {
        ps.top.x =
            ps.bottom.x =
                xbase + x + 0.5; /* Start at center of pixel at Y=127.5, bottom at Y=-0.5 */
        ps.top.y = ps.bottom.y = ybase + y + 0.5;
        ps.top.z = maxheight + 0.5;
        ps.bottom.z = minheight - 0.5;
        map_to_world.transform(ps.top); /* Transform to world coordinates */
        map_to_world.transform(ps.bottom);
        ps.py = y;
        for (int i = 0; i < numshaders; i++) {
          shaderstate[i].reset(ps);
        }
        ps.raytrace(cache, mapiter, shaderstate, shaderdone);
        for (int i = 0; i < numshaders; i++) {
          if (shaderdone[i] == false) {
            shaderstate[i].rayFinished(ps);
          } else {
            shaderdone[i] = false;
            rendered[i] = true;
          }
          shaderstate[i].getRayColor(rslt, 0);
          argb_buf[i][(tileHeight - y - 1) * tileWidth + x] = rslt.getARGB();
          if (day_argb_buf[i] != null) {
            shaderstate[i].getRayColor(rslt, 1);
            day_argb_buf[i][(tileHeight - y - 1) * tileWidth + x] = rslt.getARGB();
          }
        }
      }
    }

    boolean renderone = false;
    /* Test to see if we're unchanged from older tile */
    TileHashManager hashman = MapManager.mapman.hashman;
    for (int i = 0; i < numshaders; i++) {
      long crc = hashman.calculateTileHash(argb_buf[i]);
      boolean tile_update = false;
      String prefix = shaderstate[i].getMap().getPrefix();
      if (rendered[i]) {
        renderone = true;
        MapType.ImageFormat fmt = shaderstate[i].getMap().getImageFormat();
        String fname = tile.getFilename(prefix, fmt);
        File f = new File(tile.getDynmapWorld().worldtilepath, fname);
        FileLockManager.getWriteLock(f);
        try {
          if ((!f.exists())
              || (crc != hashman.getImageHashCode(tile.getKey(), prefix, tile.tx, tile.ty))) {
            /* Wrap buffer as buffered image */
            Debug.debug("saving image " + f.getPath());
            if (!f.getParentFile().exists()) f.getParentFile().mkdirs();
            try {
              FileLockManager.imageIOWrite(im[i].buf_img, fmt.getFileExt(), f);
            } catch (IOException e) {
              Debug.error("Failed to save image: " + f.getPath(), e);
            } catch (java.lang.NullPointerException e) {
              Debug.error("Failed to save image (NullPointerException): " + f.getPath(), e);
            }
            MapManager.mapman.pushUpdate(tile.getWorld(), new Client.Tile(fname));
            hashman.updateHashCode(tile.getKey(), prefix, tile.tx, tile.ty, crc);
            tile.getDynmapWorld().enqueueZoomOutUpdate(f);
            tile_update = true;
          } else {
            Debug.debug("skipping image " + f.getPath() + " - hash match");
          }
        } finally {
          FileLockManager.releaseWriteLock(f);
          DynmapBufferedImage.freeBufferedImage(im[i]);
        }
        MapManager.mapman.updateStatistics(tile, prefix, true, tile_update, !rendered[i]);
        /* Handle day image, if needed */
        if (dayim[i] != null) {
          fname = tile.getDayFilename(prefix, fmt);
          f = new File(tile.getDynmapWorld().worldtilepath, fname);
          FileLockManager.getWriteLock(f);
          prefix = prefix + "_day";
          tile_update = false;
          try {
            if ((!f.exists())
                || (crc != hashman.getImageHashCode(tile.getKey(), prefix, tile.tx, tile.ty))) {
              /* Wrap buffer as buffered image */
              Debug.debug("saving image " + f.getPath());
              if (!f.getParentFile().exists()) f.getParentFile().mkdirs();
              try {
                FileLockManager.imageIOWrite(dayim[i].buf_img, fmt.getFileExt(), f);
              } catch (IOException e) {
                Debug.error("Failed to save image: " + f.getPath(), e);
              } catch (java.lang.NullPointerException e) {
                Debug.error("Failed to save image (NullPointerException): " + f.getPath(), e);
              }
              MapManager.mapman.pushUpdate(tile.getWorld(), new Client.Tile(fname));
              hashman.updateHashCode(tile.getKey(), prefix, tile.tx, tile.ty, crc);
              tile.getDynmapWorld().enqueueZoomOutUpdate(f);
              tile_update = true;
            } else {
              Debug.debug("skipping image " + f.getPath() + " - hash match");
            }
          } finally {
            FileLockManager.releaseWriteLock(f);
            DynmapBufferedImage.freeBufferedImage(dayim[i]);
          }
          MapManager.mapman.updateStatistics(tile, prefix, true, tile_update, !rendered[i]);
        }
      }
    }
    return renderone;
  }