class ShadowRenderer {

   Visual source;
   public ShadowRenderer(Visual source, int shadowSize){
      this.source = source;
      sourceActualSize = source.getActualSize();
      this.shadowSize = shadowSize;
   }

   float shadowOpacity = 0.8f;
   int shadowSize = 10;
   Color shadowColor = Color.black;
   BufferedImage createShadowMask(BufferedImage image){
      BufferedImage mask = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB);

      Graphics2D g2d = mask.createGraphics();
      g2d.drawImage(image, 0, 0, null);
      // Ar = As*Ad - Cr = Cs*Ad -> extract 'Ad'
      g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_IN, shadowOpacity));
      g2d.setColor(shadowColor);
      g2d.fillRect(0, 0, image.getWidth(), image.getHeight());
      g2d.dispose();
      return mask;
   }

   ConvolveOp getBlurOp(int size) {
      float[] data = new float[size * size];
      float value = 1 / (float) (size * size);
      for (int i = 0; i < data.length; i++) {
         data[i] = value;
      }
      return new ConvolveOp(new Kernel(size, size, data));
   }

   BufferedImage shadowImage = null;
   Dimension sourceActualSize = null;
   public BufferedImage createShadowImage(){<FILL_FUNCTION_BODY>}

   public void paintComponent(Graphics g){
      Graphics2D g2d = (Graphics2D)g;

      // create shadow image if the size of the source component has changed since last rendering attempt
      if (shadowImage == null || source.getActualHeight() != sourceActualSize.height ||
            source.getActualWidth() != sourceActualSize.width){
         createShadowImage();
         sourceActualSize = source.getActualSize();
      }
      //Debug.info("[Shadow] painting shadow" + shadowImage);
      g2d.drawImage(shadowImage, 0, 0, null, null);
   }
}


      BufferedImage image = new BufferedImage(source.getActualWidth() + shadowSize * 2,
            source.getActualHeight() + shadowSize * 2, BufferedImage.TYPE_INT_ARGB);
      Graphics2D g2 = image.createGraphics();
      g2.translate(shadowSize,shadowSize);
      source.paintPlain(g2);

      shadowImage = new BufferedImage(image.getWidth(),image.getHeight(),BufferedImage.TYPE_INT_ARGB);
      getBlurOp(shadowSize).filter(createShadowMask(image), shadowImage);
      g2.dispose();

      //Debug.info("[Shadow] shadowImage: " + shadowImage);

      return shadowImage;
   
<no_super_class>